iOS17锁定横屏应用的视频播放方向校正简化方案问询
问题
我的应用已锁定为全屏横屏模式,目前按照WWDC22《使用AVFoundation和Metal以EDR格式显示HDR视频》中的方案,将视频帧转为CIImage后传入CIFilters,再通过Metal渲染。
当前我通过两步实现视频播放方向校正:
- 确定录制相机的方向
- 应用校正旋转
具体实现流程是:异步加载AVAsset的videoTracks及preferredTransform,从中解析出UIInterfaceOrientation和AVCaptureDevicePosition,再通过设备位置与方向的组合映射到对应的CGImagePropertyOrientation,最后对CIImage应用变换。但这套流程过于繁琐,想请教是否有更简便的实现方式?
现有核心代码如下:
func videoOrientation() async -> PGLDevicePosition { var orientation: UIInterfaceOrientation = .unknown var device: AVCaptureDevice.Position = .unspecified var myVideoTracks:[AVAssetTrack]? var t: CGAffineTransform = CGAffineTransformIdentity do { myVideoTracks = try await loadTracks(withMediaType: .video) } catch { /// 返回默认值 .unknown 和 .unspecificed return PGLDevicePosition(orientation: orientation, device: device) } if let videoTrack = myVideoTracks?.first { do { t = try await videoTrack.load(.preferredTransform) } catch { /// 返回默认值 .unknown 和 .unspecificed return PGLDevicePosition(orientation: orientation, device: device) }
func getVideoPreferredTransform(callBack: @escaping (PGLDevicePosition) -> Void ) { Task { let devicePosition = await avPlayerItem.asset.videoOrientation() callBack(devicePosition) } }
var result = CGImagePropertyOrientation.up // 默认值 switch (imageOrientation.orientation, imageOrientation.device) { case (.unknown,.unspecified) : result = CGImagePropertyOrientation.up case (.portrait, .front) : result = CGImagePropertyOrientation.right case (.portraitUpsideDown, .front): result = CGImagePropertyOrientation.right case (.landscapeLeft, .front) : result = CGImagePropertyOrientation.up case (.landscapeRight, .front) : result = CGImagePropertyOrientation.up case (.portrait, .back) : result = CGImagePropertyOrientation.right case (.portraitUpsideDown, .back): result = CGImagePropertyOrientation.left case (.landscapeLeft, .back) : result = CGImagePropertyOrientation.down case (.landscapeRight, .back) : result = CGImagePropertyOrientation.up default: return result // 默认 .up } return result
let sourceFrame = CIImage(cvPixelBuffer: buffer) let neededTransform = sourceFrame.orientationTransform(for: videoPropertyOrientation) videoCIFrame = sourceFrame.transformed(by: neededTransform)
简化实现方案
完全可以跳过解析UIInterfaceOrientation和AVCaptureDevicePosition的步骤,直接利用AVAssetTrack的preferredTransform来处理CIImage的方向,无需手动映射CGImagePropertyOrientation。
核心思路
AVAssetTrack的preferredTransform已经包含了视频录制时的旋转、镜像等方向信息,直接将这个变换应用到CIImage上,就能得到符合显示要求的帧。如果你的应用固定为横屏,还可以额外添加适配横屏的变换,确保最终帧的尺寸和方向匹配屏幕。
简化代码示例
- 直接获取并应用preferredTransform
// 异步加载并缓存视频轨道的preferredTransform private var cachedOrientationTransform: CGAffineTransform? func loadVideoOrientationTransform() async { guard let videoTrack = try? await asset.loadTracks(withMediaType: .video).first else { cachedOrientationTransform = CGAffineTransformIdentity return } cachedOrientationTransform = try? await videoTrack.load(.preferredTransform) ?? CGAffineTransformIdentity } // 帧处理时直接应用缓存的变换 func processFrame(buffer: CVPixelBuffer) -> CIImage { let sourceFrame = CIImage(cvPixelBuffer: buffer) guard let orientationTransform = cachedOrientationTransform else { return sourceFrame } // 适配固定横屏(根据你的横屏方向调整旋转角度,这里以顺时针90度为例) let landscapeAdaptTransform = CGAffineTransform(rotationAngle: .pi / 2) let finalTransform = orientationTransform.concatenated(with: landscapeAdaptTransform) return sourceFrame.transformed(by: finalTransform) }
- 从preferredTransform直接推导CGImagePropertyOrientation
如果仍需要使用CGImagePropertyOrientation,可以直接从preferredTransform反向推导,不用依赖设备位置:
func orientationFromTransform(_ transform: CGAffineTransform) -> CGImagePropertyOrientation { var orientation = CGImagePropertyOrientation.up switch (transform.a, transform.b, transform.c, transform.d) { case (0, 1.0, -1.0, 0): orientation = .right case (0, -1.0, 1.0, 0): orientation = .left case (-1.0, 0, 0, -1.0): orientation = .down case (1.0, 0, 0, 1.0): orientation = .up default: break } // 处理前置摄像头镜像情况 if transform.tx == 0 && transform.ty != 0 { orientation = orientation.mirrored() } return orientation } // 使用示例 let transform = try? await videoTrack.load(.preferredTransform) let orientation = orientationFromTransform(transform ?? .identity) let neededTransform = sourceFrame.orientationTransform(for: orientation) let finalFrame = sourceFrame.transformed(by: neededTransform)
额外优化
- 提前异步加载并缓存
preferredTransform,避免每帧处理时重复执行IO操作,提升渲染性能。 - 由于应用固定为横屏,可将
preferredTransform与固定横屏变换提前拼接成最终矩阵,直接复用即可。
内容的提问来源于stack exchange,提问作者Will Loew-Blosser
相关产品推荐
相关产品推荐

