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

iOS 11下iPhone X实现4K@60FPS视频录制的可用API咨询

4K@60FPS HEVC Recording on iPhone X (iOS 11.3) with AVFoundation

Hey there! Let's get your iPhone X capturing 4K@60FPS HEVC video sorted. The issue you're hitting usually comes from trying to force frame durations without first selecting a hardware-supported format. Here's the breakdown of what you need to do, plus a complete view controller example:

Key API Directions to Follow

  • Validate supported formats first: Never hardcode frame rates or resolutions—always check your camera's supportedFormats to find a format that natively supports 3840x2160 (4K) resolution, HEVC codec, and 60 FPS.
  • Set active format before adjusting frame timing: Once you've found the right format, set it as the camera's activeFormat. Only then can you safely set activeVideoMinFrameDuration and activeVideoMaxFrameDuration to 1/60th of a second.
  • Tune AVAssetWriter settings: For HEVC 4K@60FPS, use AVVideoCodecTypeHEVC and a bitrate between 40–60 Mbps (adjust based on your quality needs).

Complete View Controller Code Example

import UIKit
import AVFoundation
import Photos

class VideoRecorderVC: UIViewController {
    // MARK: - Properties
    private let captureSession = AVCaptureSession()
    private var videoInput: AVCaptureDeviceInput?
    private let videoOutput = AVCaptureVideoDataOutput()
    private var assetWriter: AVAssetWriter?
    private var assetWriterInput: AVAssetWriterInput?
    private var isRecording = false
    private let recordButton = UIButton(type: .system)
    
    // MARK: - Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        setupSession()
        setupPreviewLayer()
        setupRecordButton()
        requestPermissions()
    }
    
    // MARK: - Setup
    private func requestPermissions() {
        AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
            guard granted else {
                DispatchQueue.main.async {
                    self?.showPermissionError()
                }
                return
            }
            DispatchQueue.main.async {
                self?.startSession()
            }
        }
    }
    
    private func setupSession() {
        captureSession.sessionPreset = .high // We'll override with custom format
        
        // Get back camera
        guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {
            fatalError("Back camera not available")
        }
        
        // Find 4K@60FPS HEVC format
        guard let targetFormat = find4K60HEVCFormat(for: camera) else {
            fatalError("4K@60FPS HEVC format not supported on this device")
        }
        
        // Configure camera settings
        do {
            try camera.lockForConfiguration()
            camera.activeFormat = targetFormat
            // Set frame duration to 60 FPS
            camera.activeVideoMinFrameDuration = CMTimeMake(value: 1, timescale: 60)
            camera.activeVideoMaxFrameDuration = CMTimeMake(value: 1, timescale: 60)
            camera.unlockForConfiguration()
        } catch {
            fatalError("Failed to configure camera: \(error.localizedDescription)")
        }
        
        // Create video input
        do {
            videoInput = try AVCaptureDeviceInput(device: camera)
            if captureSession.canAddInput(videoInput!) {
                captureSession.addInput(videoInput!)
            }
        } catch {
            fatalError("Failed to create video input: \(error.localizedDescription)")
        }
        
        // Configure video output
        videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue"))
        videoOutput.alwaysDiscardsLateVideoFrames = true
        if captureSession.canAddOutput(videoOutput) {
            captureSession.addOutput(videoOutput)
        }
        
        // Set output connection to landscape (matches 4K resolution)
        if let connection = videoOutput.connection(with: .video) {
            if connection.isVideoOrientationSupported {
                connection.videoOrientation = .landscapeRight
            }
        }
    }
    
    private func setupPreviewLayer() {
        let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        previewLayer.frame = view.bounds
        previewLayer.videoGravity = .resizeAspectFill
        view.layer.addSublayer(previewLayer)
    }
    
    private func setupRecordButton() {
        recordButton.setTitle("Start Recording", for: .normal)
        recordButton.titleLabel?.font = UIFont.systemFont(ofSize: 20, weight: .bold)
        recordButton.addTarget(self, action: #selector(toggleRecording), for: .touchUpInside)
        recordButton.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(recordButton)
        
        NSLayoutConstraint.activate([
            recordButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
            recordButton.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        ])
    }
    
    // MARK: - Format Helper
    private func find4K60HEVCFormat(for device: AVCaptureDevice) -> AVCaptureDevice.Format? {
        let targetResolution = CGSize(width: 3840, height: 2160)
        let targetFrameRate = CMTimeMake(value: 1, timescale: 60)
        
        for format in device.supportedFormats {
            let formatDescription = format.formatDescription
            guard let dimensions = CMVideoFormatDescriptionGetDimensions(formatDescription) else { continue }
            
            // Check if resolution is 4K
            guard CGSize(width: dimensions.width, height: dimensions.height) == targetResolution else { continue }
            
            // Check if format supports HEVC (h.265)
            guard CMFormatDescriptionGetMediaSubType(formatDescription) == kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange else { continue }
            
            // Check if 60 FPS is supported
            let supportedFrameRates = format.videoSupportedFrameRateRanges
            for range in supportedFrameRates {
                if range.minFrameRate <= targetFrameRate.timescale / targetFrameRate.value && range.maxFrameRate >= targetFrameRate.timescale / targetFrameRate.value {
                    return format
                }
            }
        }
        return nil
    }
    
    // MARK: - Recording Control
    @objc private func toggleRecording() {
        isRecording.toggle()
        if isRecording {
            startRecording()
            recordButton.setTitle("Stop Recording", for: .normal)
        } else {
            stopRecording()
            recordButton.setTitle("Start Recording", for: .normal)
        }
    }
    
    private func startRecording() {
        // Create output file URL
        let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        let outputURL = documentsDir.appendingPathComponent("4K60Record_\(Date().timeIntervalSince1970).mov")
        
        // Create asset writer
        do {
            assetWriter = try AVAssetWriter(outputURL: outputURL, fileType: .mov)
        } catch {
            print("Failed to create asset writer: \(error.localizedDescription)")
            return
        }
        
        // Configure video input settings
        let videoSettings: [String: Any] = [
            AVVideoCodecKey: AVVideoCodecTypeHEVC,
            AVVideoWidthKey: 3840,
            AVVideoHeightKey: 2160,
            AVVideoCompressionPropertiesKey: [
                AVVideoAverageBitRateKey: 50_000_000, // 50 Mbps
                AVVideoMaxKeyFrameIntervalKey: 60 // Keyframe every 1 second
            ]
        ]
        
        assetWriterInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings)
        assetWriterInput?.expectsMediaDataInRealTime = true
        
        if let input = assetWriterInput, assetWriter?.canAdd(input) ?? false {
            assetWriter?.add(input)
        }
        
        // Start writing
        assetWriter?.startWriting()
        assetWriter?.startSession(atSourceTime: .zero)
    }
    
    private func stopRecording() {
        assetWriterInput?.markAsFinished()
        assetWriter?.finishWriting { [weak self] in
            guard let self = self else { return }
            guard let outputURL = self.assetWriter?.outputURL else { return }
            
            // Save to photo library
            PHPhotoLibrary.shared().performChanges({
                PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputURL)
            }, completionHandler: { success, error in
                if success {
                    DispatchQueue.main.async {
                        self.showSuccessMessage()
                    }
                } else {
                    print("Failed to save video: \(error?.localizedDescription ?? "Unknown error")")
                }
            })
        }
    }
    
    // MARK: - Helpers
    private func startSession() {
        if !captureSession.isRunning {
            DispatchQueue.global(qos: .userInitiated).async {
                self.captureSession.startRunning()
            }
        }
    }
    
    private func showPermissionError() {
        let alert = UIAlertController(title: "Permission Required", message: "Please enable camera access in Settings to record video.", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
    
    private func showSuccessMessage() {
        let alert = UIAlertController(title: "Success", message: "Video saved to photo library!", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

// MARK: - AVCaptureVideoDataOutputSampleBufferDelegate
extension VideoRecorderVC: AVCaptureVideoDataOutputSampleBufferDelegate {
    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        guard isRecording, let input = assetWriterInput, input.isReadyForMoreMediaData else { return }
        
        if assetWriter?.status == .writing {
            input.append(sampleBuffer)
        }
    }
}

Important Notes

  • iOS 11.3 Compatibility: iPhone X fully supports 4K@60FPS HEVC recording starting from iOS 11, so this code will work for your setup.
  • Bitrate Adjustment: The 50 Mbps bitrate is a starting point—you can lower it to reduce file size or increase it for higher quality.
  • Error Handling: The example includes basic error handling, but you might want to expand it for production use (e.g., handling asset writer failures, format not found cases).

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 06:42:17