如何将视频方向固定为纵向模式

问题描述 投票:0回答:1

在此代码中,我们将相机预览为镜像效果,然后将其水平翻转,同时将其录制到文件中以避免镜像效果,但在录制过程中,视频方向设置为上下颠倒,人脸位于相机底部腿位于相机的顶部,这是错误的。现在,我们如何解决这个问题并始终以纵向模式保存?请在这方面帮助我。谢谢。这是我的代码片段 -

import AVFoundation
import Accelerate.vImage
import UIKit
import CoreImage

enum CameraState {
    case idle, start, capturing, end
}

final class VideoDataManager: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
    
    /// Delegate to receive the frames captured by the device's camera.
    var delegate: VideoServiceProtocol?
    var cameraState = CameraState.idle
    
    private var _videoOutput: AVCaptureVideoDataOutput?
    private var assetWriter: AVAssetWriter?
    private var assetWriterInput: AVAssetWriterInput?
    private var adpater: AVAssetWriterInputPixelBufferAdaptor?
    private var time: Double = 0
    
    override init() {
        super.init()
        configureSession("FRONT")
    }
    
    /// Start capturing frames from the camera.
    func startRunning() {
        DispatchQueue.global(qos: .background).async {
            self.captureSession.startRunning()
        }
    }
    
    /// Stop capturing frames from the camera.
    func stopRunning() {
        captureSession.stopRunning()
    }
    
    var captureSession = AVCaptureSession()
    
    func switchCamera(_ cameraType:String?) {
        
        let session: AVCaptureSession = captureSession
        session.beginConfiguration()
        
        var position = AVCaptureDevice.Position.back
        if cameraType == "FRONT" {
            position = AVCaptureDevice.Position.front
        }
        
        guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: position) else { return }
        
        for input in session.inputs {
            session.removeInput(input)
        }
        
        do {
            let input = try AVCaptureDeviceInput(device: camera)
            session.addInput(input)
        } catch {
            return
        }
        
        if let videoOutput =  session.outputs.first {
            // Handle mirroring based on cameraType for preview
            if let connection = videoOutput.connection(with: .video), connection.isVideoMirroringSupported {
                connection.isVideoMirrored = position == .front
            }
            videoOutput.connection(with: .video)?.videoOrientation = .portrait
        }
        
        session.commitConfiguration()
        captureSession = session
        
    }
    
    /// Initialize the capture session.
    ///
    func configureSession(_ cameraType:String?) {
        
        if captureSession.isRunning {
            stopRunning()
            for input in captureSession.inputs {
                captureSession.removeInput(input)
            }
        }
        
        captureSession.sessionPreset = AVCaptureSession.Preset.photo
        var position = AVCaptureDevice.Position.back
        
        if cameraType == "FRONT" {
            position = AVCaptureDevice.Position.front
        }
        
        guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: position) else { return }
        
        do {
            let input = try AVCaptureDeviceInput(device: camera)
            captureSession.addInput(input)
        } catch {
            return
        }
        
        let videoOutput = AVCaptureVideoDataOutput()
        videoOutput.videoSettings = [
            (kCVPixelBufferPixelFormatTypeKey as String): NSNumber(value: kCVPixelFormatType_32BGRA)
        ]
        videoOutput.alwaysDiscardsLateVideoFrames = true
        
        let dataOutputQueue = DispatchQueue(label: "video data queue", qos: .userInitiated, attributes: [], autoreleaseFrequency: .workItem)
        
        if captureSession.canAddOutput(videoOutput) {
            captureSession.addOutput(videoOutput)
            if let connection = videoOutput.connection(with: .video), connection.isVideoMirroringSupported {
                connection.isVideoMirrored = position == .front
                connection.videoOrientation = .portrait
            }
        }
        
        videoOutput.setSampleBufferDelegate(self, queue: dataOutputQueue)
        _videoOutput = videoOutput
        
    }
    
    // MARK: Methods of the AVCaptureVideoDataOutputSampleBufferDelegate
    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        
        guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
            return
        }
        
        CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.readOnly)
        delegate?.feedVideoDataIntoModel(didOutput: pixelBuffer)
        CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.readOnly)
        
        let timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer).seconds
        
        switch cameraState {
        case .start:
            
            // Set up recorder
            guard let fileUrl = Utility.getExcerciseRecordingFilePath() else { return }
            
            try? FileManager.default.removeItem(at: fileUrl)
            let writer = try! AVAssetWriter(outputURL: fileUrl, fileType: .mp4)
            let settings = _videoOutput?.recommendedVideoSettingsForAssetWriter(writingTo: .mp4)
            let input = AVAssetWriterInput(mediaType: .video, outputSettings: settings)
            input.mediaTimeScale = CMTimeScale(bitPattern: 600)
            input.expectsMediaDataInRealTime = true
            let adapter = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: input, sourcePixelBufferAttributes: nil)
            if writer.canAdd(input) {
                writer.add(input)
            }
            input.transform = getVideoTransform(for: connection.videoOrientation)
            writer.startWriting()
            writer.startSession(atSourceTime: .zero)
            assetWriter = writer
            assetWriterInput = input
            adpater = adapter
            cameraState = .capturing
            time = timestamp
            MMHLogger.smartLog("Camera State.... start...")
            
        case .capturing:
            MMHLogger.smartLog("Video is capturing.... in capturing state")
            MMHLogger.smartLog("Video isReady in capturing state Mode:>>>>>>\(String(describing: assetWriterInput?.isReadyForMoreMediaData))")
            
            if let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
                if assetWriterInput?.isReadyForMoreMediaData == true {
                    let transformedPixelBuffer = applyMirrorEffectRemovalTransformation(pixelBuffer, originalOrientation: connection.videoOrientation)
                    let time = CMTime(seconds: timestamp - time, preferredTimescale: CMTimeScale(600))
                    adpater?.append(transformedPixelBuffer, withPresentationTime: time)
                    
                }
            }
            break
            
        case .end:
            MMHLogger.smartLog("Video is ended in end state....")
            MMHLogger.smartLog("Video isReady in end state Mode:>>>>>>\(String(describing: assetWriterInput?.isReadyForMoreMediaData))")
            guard assetWriterInput?.isReadyForMoreMediaData == true, assetWriter!.status != .failed else { break }
            
            assetWriterInput?.markAsFinished()
            assetWriter?.finishWriting { [weak self] in
                self?.assetWriter = nil
                self?.assetWriterInput = nil
                MMHLogger.smartLog("Video is recorded")
                NotificationCenter.default.post(name: Notification.Name("STOPRECORDING"), object: nil)
            }
            
        default:
            break
        }
    }
    
    func applyMirrorEffectRemovalTransformation(_ pixelBuffer: CVPixelBuffer, originalOrientation: AVCaptureVideoOrientation) -> CVPixelBuffer {
        let mirroredPixelBuffer = createMirroredPixelBuffer(pixelBuffer)
        return mirroredPixelBuffer
    }
    
    // Create a mirrored pixel buffer
    func createMirroredPixelBuffer(_ pixelBuffer: CVPixelBuffer) -> CVPixelBuffer {
        var mirroredPixelBuffer: CVPixelBuffer?
        
        CVPixelBufferCreate(nil, CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer), CVPixelBufferGetPixelFormatType(pixelBuffer), nil, &mirroredPixelBuffer)
        
        guard let mirroredPixelBuffer = mirroredPixelBuffer else {
            return pixelBuffer
        }
        
        CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly)
        CVPixelBufferLockBaseAddress(mirroredPixelBuffer, [])
        
        let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer)
        let mirroredBaseAddress = CVPixelBufferGetBaseAddress(mirroredPixelBuffer)
        
        let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer)
        let mirroredBytesPerRow = CVPixelBufferGetBytesPerRow(mirroredPixelBuffer)
        for y in 0..<CVPixelBufferGetHeight(pixelBuffer) {
            memcpy(mirroredBaseAddress! + y * mirroredBytesPerRow, baseAddress! + (CVPixelBufferGetHeight(pixelBuffer) - y - 1) * bytesPerRow, bytesPerRow)
        }
        
        CVPixelBufferUnlockBaseAddress(mirroredPixelBuffer, [])
        CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly)
        
        return mirroredPixelBuffer
    }
    
    // Update the getVideoTransform method to accept an orientation parameter
    private func getVideoTransform(for orientation: AVCaptureVideoOrientation) -> CGAffineTransform {
        switch orientation {
        case .portraitUpsideDown:
            return CGAffineTransform(rotationAngle: .pi)
        case .landscapeLeft:
            return CGAffineTransform(rotationAngle: .pi / 2)
        case .landscapeRight:
            return CGAffineTransform(rotationAngle: -.pi / 2)
        default:
            return CGAffineTransform.identity
        }
    }
    
    func capture(state: CameraState) {
        switch state {
        case .idle:
            cameraState = .idle
        case .start:
            cameraState = .start
        case .capturing:
            cameraState = .capturing
        case .end:
            cameraState = .end
        }
    }
}

编辑:

我还再次尝试更改视频方向并将其再次保存在另一个 DD 文件中,以使该视频始终处于纵向模式,但不幸的是失败了,在新文件中再次导出时显示“无法保存”错误消息。

func rotateAndSaveVideo(url: URL) {
    let asset = AVAsset(url: url)
    
    guard let rotatedFileURL = getWritableFileURL(),
            FileManager.default.isWritableFile(atPath: rotatedFileURL.path) else {
        return
    }
    
    // Create a video composition
    let composition = AVMutableComposition()
    
    // Add the video track
    guard let videoTrack = composition.addMutableTrack(
        withMediaType: .video,
        preferredTrackID: kCMPersistentTrackID_Invalid
    ) else {
        return
    }
    
    // Load the source video track
    if let sourceVideoTrack = asset.tracks(withMediaType: .video).first {
        try? videoTrack.insertTimeRange(
            CMTimeRange(start: .zero, duration: asset.duration),
            of: sourceVideoTrack,
            at: .zero
        )
        
        // Apply rotation to the video track
        let transform = sourceVideoTrack.preferredTransform.rotated(by: .pi)
        videoTrack.preferredTransform = transform
        
        // Export the rotated video
        if let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality) {
            exporter.outputURL = rotatedFileURL
            exporter.outputFileType = .mp4
            exporter.exportAsynchronously(completionHandler: {
                switch exporter.status {
                case .completed:
                    print("Succeed")
                case .failed:
                    if let error = exporter.error {
                        print("Export failed with error: \(error.localizedDescription)")
                    }
                default:
                    break
                }
            })
        }
    }
}

func getWritableFileURL() -> URL? {
    // Get the documents directory URL
    guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
        return nil
    }
    
    // Append a unique filename to the documents directory URL
    let fileName = "rotatedVideo.mp4" // You can choose any desired filename
    let rotatedFileURL = documentsDirectory.appendingPathComponent(fileName)
    
    // Check if the file is writable or create the necessary directories
    do {
        if FileManager.default.isWritableFile(atPath: rotatedFileURL.path) {
            return rotatedFileURL
        } else {
            try FileManager.default.createDirectory(
                at: rotatedFileURL,
                withIntermediateDirectories: true,
                attributes: nil
            )
            return rotatedFileURL
        }
    } catch {
        print("Error creating directories: \(error.localizedDescription)")
        return nil
    }
}
ios swift avcapturesession avassetwriterinput avcapturevideodataoutput
1个回答
0
投票

将相机方向旋转 180 度后解决了此问题 -

extension VideoDataManager {
    
    func rotateAndSaveVideo(url: URL) {
        let asset = AVAsset(url: url)
        
        guard let rotatedFileURL = getWritableFileURL() else { return }
        
        print("Destination URL: \(rotatedFileURL)")
        
        // Create a video composition
        let composition = AVMutableComposition()
        
        // Add the video track
        guard let videoTrack = composition.addMutableTrack(
            withMediaType: .video,
            preferredTrackID: kCMPersistentTrackID_Invalid
        ) else { return }
        
        // Load the source video track
        if let sourceVideoTrack = asset.tracks(withMediaType: .video).first {
            try? videoTrack.insertTimeRange(
                CMTimeRange(start: .zero, duration: asset.duration),
                of: sourceVideoTrack,
                at: .zero
            )
            
            // Apply rotation to the video track
            let transform = sourceVideoTrack.preferredTransform.rotated(by: .pi)
            videoTrack.preferredTransform = transform
            
            // Export the rotated video
            if let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality) {
                exporter.outputURL = rotatedFileURL
                exporter.outputFileType = .mp4
                exporter.exportAsynchronously(completionHandler: {
                    switch exporter.status {
                    case .completed:
                        print("Export succeeded")
                    case .failed:
                        if let error = exporter.error {
                            print("Export failed with error: \(error)")
                        } else {
                            print("Export failed with an unknown error")
                        }
                    default:
                        break
                    }
                })
            }
        }
    }
    
    func getWritableFileURL() -> URL? {
        // Get the documents directory URL
        guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
            return nil
        }
        
        
        // Append a unique filename to the documents directory URL
        let fileName = "rotatedVideo.mp4" // You can choose any desired filename
        let rotatedFileURL = documentsDirectory.appendingPathComponent(fileName)
        
        debugPrint(Utility.getExcerciseRecordingRotatedFilePath() ?? "mmm")
        debugPrint(rotatedFileURL)
        
        // Remove the file if it already exists
        do {
            if FileManager.default.fileExists(atPath: rotatedFileURL.path) {
                try FileManager.default.removeItem(at: rotatedFileURL)
            }
        } catch {
            print("Error removing existing file: \(error.localizedDescription)")
        }
        
        // Check if the file is writable or create the necessary directories
        do {
            if FileManager.default.isWritableFile(atPath: rotatedFileURL.path) {
                return rotatedFileURL
            } else {
                try FileManager.default.createDirectory(
                    at: documentsDirectory,
                    withIntermediateDirectories: true,
                    attributes: nil
                )
                return rotatedFileURL
            }
        } catch {
            print("Error creating directories: \(error.localizedDescription)")
            return nil
        }
    }
}

从 - 结束捕获部分调用此方法

        guard let url = Utility.getExcerciseRecordingFilePath() else { return }
        self?.rotateAndSaveVideo(url: url)
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
            NotificationCenter.default.post(name: Notification.Name("STOPRECORDING"), object: nil)
        }
© www.soinside.com 2019 - 2024. All rights reserved.