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

iOS17锁定横屏应用的视频播放方向校正简化方案问询

问题

我的应用已锁定为全屏横屏模式,目前按照WWDC22《使用AVFoundation和Metal以EDR格式显示HDR视频》中的方案,将视频帧转为CIImage后传入CIFilters,再通过Metal渲染。

当前我通过两步实现视频播放方向校正:

  1. 确定录制相机的方向
  2. 应用校正旋转

具体实现流程是:异步加载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上,就能得到符合显示要求的帧。如果你的应用固定为横屏,还可以额外添加适配横屏的变换,确保最终帧的尺寸和方向匹配屏幕。

简化代码示例

  1. 直接获取并应用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)
}
  1. 从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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 09:53:20