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
supportedFormatsto 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 setactiveVideoMinFrameDurationandactiveVideoMaxFrameDurationto 1/60th of a second. - Tune AVAssetWriter settings: For HEVC 4K@60FPS, use
AVVideoCodecTypeHEVCand 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
相关产品推荐
相关产品推荐

