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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 02:33:08