AVCaptureFileOutput startRecording代理方法不触发问题
问题描述
参考苹果官方文档编写简易视频录制Demo,代码运行无报错,但调用startRecording()方法传入代理时,代理实现的录制开始、录制结束回调方法始终无法执行。注意到苹果官方示例代码将所有相关逻辑都整合在同一个类中实现,想确认是否必须采用这种写法才能让录制功能正常生效。
复现代码
class Delegate:NSObject,AVCaptureFileOutputRecordingDelegate{ func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) { func cleanup(){ let path = outputFileURL.path if(FileManager.default.fileExists(atPath: path)){ do{ try FileManager.default.removeItem(atPath: path) } catch{ print("error") } } } if error != nil { print("error") } PHPhotoLibrary.requestAuthorization(for: .readWrite){ status in if status == .authorized{ PHPhotoLibrary.shared().performChanges{ let option = PHAssetResourceCreationOptions() option.shouldMoveFile=true let createrequset = PHAssetCreationRequest.forAsset() createrequset.addResource(with: .video, fileURL: outputFileURL, options: option) } completionHandler:{ a,b in if !a { print("error") } cleanup() } } else{cleanup()} } } func fileOutput(_ output: AVCaptureFileOutput, didStartRecordingTo fileURL: URL, from connections: [AVCaptureConnection]) { print("didstart") } } struct ContentView: View { private var movieFileOutput: AVCaptureMovieFileOutput = AVCaptureMovieFileOutput() private let sessionQueue = DispatchQueue(label: "session queue") let a = AVCaptureSession() var body: some View { ZStack { CameraPreview(session: a) .ignoresSafeArea() .onAppear{ let status = AVCaptureDevice.authorizationStatus(for: .video) switch status { case .notDetermined: AVCaptureDevice.requestAccess(for: .video){test in if test { print("settingup") setUpCamera() } } case .restricted: return case .denied: fatalError() case .authorized: setUpCamera() print("settingup") a.startRunning() @unknown default: fatalError() } } Button("record"){ record() } } } func record(){ sessionQueue.async { if movieFileOutput.isRecording == false{ let movieOutputConnetion = movieFileOutput.connection(with: .video)! movieOutputConnetion.videoOrientation = .portrait movieFileOutput.setOutputSettings([AVVideoCodecKey:AVVideoCodecType.h264], for: movieOutputConnetion) movieOutputConnetion.videoOrientation = .portrait let outputFileName = UUID().uuidString let outputFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent((outputFileName as NSString).appendingPathExtension("mov")!) print("starting") print(movieFileOutput.connections) movieFileOutput.startRecording(to: URL(fileURLWithPath: outputFilePath), recordingDelegate: Delegate()) } else {movieFileOutput.stopRecording()} } } func setUpCamera(){ a.beginConfiguration() let device = AVCaptureDevice.default(.builtInTelephotoCamera,for:.video,position: .unspecified) guard let videoDeviceInput = try? AVCaptureDeviceInput(device:device!), a.canAddInput(videoDeviceInput) else {return} print("added") a.addInput(videoDeviceInput) let vedio = AVCaptureVideoDataOutput() guard a.canAddOutput(vedio)else{return} a.sessionPreset = .hd4K3840x2160 a.addOutput(movieFileOutput) print(a.connections) a.commitConfiguration() } }
原因说明&修复方案
不需要将所有逻辑整合到同一个类中才能让录制生效,官方示例的写法只是演示用的单类实现,代理单独拆分类完全可以正常工作。回调不触发的核心原因和修复点如下:
- 核心问题:代理对象被提前释放。
AVCaptureFileOutput的recordingDelegate属性是weak弱引用,不会持有传入的代理对象。你在调用startRecording时临时创建Delegate()实例,没有任何强引用指向这个实例,代码执行完后该实例会被ARC立即回收,自然无法收到任何回调。修复方式是在ContentView中新增一个强引用属性持有代理实例:
调用// 在ContentView的属性中新增 private let recordingDelegate = Delegate()startRecording时传入这个持有的实例,替换掉临时创建的写法:movieFileOutput.startRecording(to: URL(fileURLWithPath: outputFilePath), recordingDelegate: recordingDelegate) - 缺失音频采集配置:当前代码仅添加了视频输入,没有添加麦克风音频输入、也没有申请麦克风权限,部分系统版本下缺失音频轨道的录制会静默失败,不触发回调。需要在Info.plist中添加麦克风权限描述,同时在
setUpCamera方法中补充音频输入逻辑:// 配置音频输入 guard let audioDevice = AVCaptureDevice.default(for: .audio), let audioInput = try? AVCaptureDeviceInput(device: audioDevice), a.canAddInput(audioInput) else { return } a.addInput(audioInput) - 线程调用不规范:
AVCaptureSession的所有配置、启停操作都应该放到你预先定义的sessionQueue串行队列中执行,不要直接在主线程onAppear回调中调用setUpCamera和a.startRunning(),避免线程竞争导致的输出异常。 - 冗余代码清理:
setUpCamera中创建了AVCaptureVideoDataOutput实例但没有实际添加到会话,这段冗余逻辑可以直接删除,避免不必要的资源占用和潜在冲突。
内容的提问来源于stack exchange,提问作者Perry Wang
相关产品推荐
相关产品推荐

