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

Swift中如何使AVCaptureFileOutput输出无旋转EXIF的正确方向视频?

解决AVCaptureFileOutput直接输出无EXIF旋转的正确方向视频(Swift实现)

我之前帮不少开发者解决过这个问题——AVFoundation默认录制的横屏视频会带EXIF旋转标记,导致后期用AVComposition导出时方向错乱。要让AVCaptureFileOutput直接录制出像素方向正确、不带旋转EXIF参数的横屏视频,核心是让采集会话在录制阶段就把视频帧旋转到目标方向,而不是依赖后期的EXIF标记。下面是完整的Swift实现方案和关键细节:

核心思路

默认情况下,摄像头采集的原始帧方向和UI横屏方向不匹配,系统会自动添加EXIF旋转参数来让播放器正确显示。我们需要通过配置AVCaptureConnection的视频方向,告诉录制输出直接将帧旋转为目标方向,这样输出的视频本身的像素排列就是正确的,无需EXIF辅助。

完整Swift实现代码

1. 定义录制器类

import AVFoundation
import UIKit

class VideoRecorder: NSObject {
    // 采集会话
    private let captureSession = AVCaptureSession()
    // 视频文件输出
    private let movieFileOutput = AVCaptureMovieFileOutput()
    // 当前摄像头位置
    private var currentCameraPosition: AVCaptureDevice.Position = .back
    // 预览层(如果需要实时预览)
    private(set) var previewLayer: AVCaptureVideoPreviewLayer?
    
    // 初始化时配置会话
    override init() {
        super.init()
        setupCaptureSession()
        setupPreviewLayer()
    }
}

2. 配置采集会话(关键部分)

extension VideoRecorder {
    private func setupCaptureSession() {
        captureSession.beginConfiguration()
        
        // 选择支持横屏分辨率的会话预设(比如1080P)
        captureSession.sessionPreset = .hd1920x1080
        
        // 添加摄像头输入
        guard let cameraDevice = getCamera(for: currentCameraPosition),
              let videoInput = try? AVCaptureDeviceInput(device: cameraDevice),
              captureSession.canAddInput(videoInput) else {
            print("摄像头输入配置失败")
            return
        }
        captureSession.addInput(videoInput)
        
        // 添加视频文件输出
        guard captureSession.canAddOutput(movieFileOutput) else {
            print("无法添加视频输出")
            return
        }
        captureSession.addOutput(movieFileOutput)
        
        // 配置视频连接的方向(核心!)
        if let videoConnection = movieFileOutput.connection(with: .video) {
            // 设置目标横屏方向,支持landscapeRight或landscapeLeft
            let targetOrientation: AVCaptureVideoOrientation = .landscapeRight
            
            // 检查连接是否支持该方向
            if videoConnection.isVideoOrientationSupported {
                videoConnection.videoOrientation = targetOrientation
            }
            
            // 按需开启视频稳定(不影响方向设置)
            if videoConnection.isVideoStabilizationSupported {
                videoConnection.preferredVideoStabilizationMode = .auto
            }
        }
        
        captureSession.commitConfiguration()
    }
    
    // 获取指定位置的摄像头
    private func getCamera(for position: AVCaptureDevice.Position) -> AVCaptureDevice? {
        let discoverySession = AVCaptureDevice.DiscoverySession(
            deviceTypes: [.builtInWideAngleCamera],
            mediaType: .video,
            position: .unspecified
        )
        return discoverySession.devices.first { $0.position == position }
    }
}

3. 预览层配置(可选,用于实时预览)

extension VideoRecorder {
    private func setupPreviewLayer() {
        previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        previewLayer?.videoGravity = .resizeAspectFill
        // 预览层方向会自动跟随视频连接的设置,无需额外调整
    }
    
    // 启动/停止采集会话
    func startSession() {
        if !captureSession.isRunning {
            captureSession.startRunning()
        }
    }
    
    func stopSession() {
        if captureSession.isRunning {
            captureSession.stopRunning()
        }
    }
}

4. 录制控制与代理实现

extension VideoRecorder: AVCaptureFileOutputRecordingDelegate {
    func startRecording(to outputURL: URL) {
        guard !movieFileOutput.isRecording else { return }
        movieFileOutput.startRecording(to: outputURL, recordingDelegate: self)
    }
    
    func stopRecording() {
        guard movieFileOutput.isRecording else { return }
        movieFileOutput.stopRecording()
    }
    
    // 录制完成回调
    func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
        if let error = error {
            print("录制出错: \(error.localizedDescription)")
            return
        }
        print("视频录制完成,路径: \(outputFileURL.path)")
        // 可通过exiftool工具验证视频是否无Rotation参数
    }
}

5. 动态调整方向(如果需要支持录制中切换横屏方向)

如果需要在录制过程中响应设备横屏方向变化,可以添加以下方法:

extension VideoRecorder {
    func updateOrientation(for interfaceOrientation: UIInterfaceOrientation) {
        guard let videoConnection = movieFileOutput.connection(with: .video),
              videoConnection.isVideoOrientationSupported else { return }
        
        let captureOrientation: AVCaptureVideoOrientation
        switch interfaceOrientation {
        case .landscapeLeft:
            captureOrientation = .landscapeLeft
        case .landscapeRight:
            captureOrientation = .landscapeRight
        default:
            // 非横屏时默认 fallback 到目标横屏方向
            captureOrientation = .landscapeRight
        }
        
        videoConnection.videoOrientation = captureOrientation
    }
}

关键细节说明

  1. 为什么设置videoOrientation有效?
    这个属性会告诉AVCaptureConnection在将帧传递给输出前,直接旋转到指定方向。这样录制的视频像素本身就是正确的横屏排列,系统不会添加EXIF旋转标记。

  2. 前置摄像头的适配
    前置摄像头的传感器原始方向和后置相反,但AVCaptureVideoOrientation是基于UI方向的,无需额外处理——系统会自动适配前置摄像头的镜像和方向差异。

  3. 会话预设的选择
    确保选择的sessionPreset支持横屏分辨率,比如.hd1920x1080是16:9的横屏预设,不要选.photo这类方形预设,否则方向设置可能无效。

  4. 验证输出视频
    录制完成后,可以用exiftool命令行工具查看视频的EXIF信息:

    exiftool your_video.mp4
    

    如果没有Rotation字段,说明视频方向正确且无EXIF旋转标记。

内容的提问来源于stack exchange,提问作者DevStarlight

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:29:37