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

AVFoundation(Objective-C,iOS16):captureOutput方法未调用求助

问题解决:AVCaptureVideoDataOutputDelegate方法未触发

核心问题分析

从日志* Head is somehow nil (count: 0)可明确:captureOutput:didOutputSampleBuffer:fromConnection:从未被调用,帧数据完全未被捕获。问题集中在相机权限、会话配置、生命周期管理三个核心环节。


解决方案步骤

1. 强制添加相机权限校验与请求

iOS 10+访问相机必须获得用户授权,未授权的会话无法输出任何帧数据。在start:方法开头加入权限处理逻辑:

-(BOOL)start:(int)deviceIndex
{
    // 先校验相机权限
    AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    if (status == AVAuthorizationStatusNotDetermined) {
        // 异步请求权限
        [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
            if (!granted) {
                log_debug("* 相机权限被拒绝\n");
                return;
            }
            // 授权成功后异步初始化会话
            dispatch_async(dispatch_get_main_queue(), ^{
                [self setupSessionWithDeviceIndex:deviceIndex];
            });
        }];
        return YES; // 先返回成功,后续异步处理会话
    } else if (status == AVAuthorizationStatusDenied || status == AVAuthorizationStatusRestricted) {
        log_debug("* 相机权限已拒绝或受限\n");
        return NO;
    }
    
    // 权限已授权,直接初始化会话
    return [self setupSessionWithDeviceIndex:deviceIndex];
}

// 抽离会话初始化逻辑到单独方法
-(BOOL)setupSessionWithDeviceIndex:(int)deviceIndex
{
    // 复制原start方法中除权限检查外的所有初始化代码
    // 从session创建到[session startRunning]的全部逻辑
}

同时在Info.plist中添加权限描述:

<key>NSCameraUsageDescription</key>
<string>需要访问相机进行画面捕获</string>

2. 修正视频输出连接配置

添加AVCaptureVideoDataOutput后,需明确启用视频连接,避免连接未激活导致无帧输出:

[session addOutput:output];
// 新增:检查并启用视频连接
AVCaptureConnection *videoConnection = [output connectionWithMediaType:AVMediaTypeVideo];
if (videoConnection && !videoConnection.enabled) {
    videoConnection.enabled = YES;
}
// 可选:根据需求设置视频方向
if (videoConnection.isVideoOrientationSupported) {
    videoConnection.videoOrientation = AVCaptureVideoOrientationPortrait;
}

3. 替换全局变量为实例变量

原代码中session、head、count为全局变量,极易引发生命周期混乱,改为私有实例变量:

@interface Cam ()
{
    AVCaptureSession *_session;
    CVImageBufferRef _head;
    int _count;
}
// 保留原有方法声明
@end

实现中所有全局变量替换为_session、_head、_count,避免全局引用导致的意外释放或引用错误。

4. 调整帧获取的异步逻辑

[session startRunning]是异步操作,不能在调用后立刻同步阻塞等待帧。修改getFrame为异步回调,避免阻塞主线程:

- (void)getFrameWithCompletion:(void(^)(NSData *frame))completion {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        int timer = 0;
        while (timer < 500 && _count <= 5) {
            usleep(10000);
            timer++;
        }
        
        @synchronized (self) {
            if (_head == nil) {
                log_debug("* Head is somehow nil (count: %d)\n", _count);
                dispatch_async(dispatch_get_main_queue(), ^{
                    completion(nil);
                });
                return;
            }
            
            // 原有帧转换逻辑
            CIImage *ciImage = [[CIImage imageWithCVPixelBuffer:_head] imageByApplyingOrientation:6];
            CIContext *temporaryContext = [CIContext contextWithOptions:nil];
            CGImageRef videoImage = [temporaryContext createCGImage:ciImage 
                                                           fromRect:CGRectMake(0, 0,
                                                                              CVPixelBufferGetHeight(_head),
                                                                              CVPixelBufferGetWidth(_head))];
            UIImage *uiImage = [[UIImage alloc] initWithCGImage:videoImage];
            NSData *frame = UIImageJPEGRepresentation(uiImage, 1.0);
            CGImageRelease(videoImage);
            
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(frame);
            });
        }
    });
}

调用方式同步修改为异步:

@autoreleasepool
{
    cam = [[Cam alloc] init];
    if ([cam start:camID]) {
        [cam getFrameWithCompletion:^(NSData *frame) {
            if (frame == nil)
                log_debug("* Frame is somehow nil?\n");
            else {
                // 处理获取到的帧数据
            }
        }];
    } else {
        cam = nil;
    }
}

5. 添加调试日志确认方法触发

在captureOutput方法开头加入日志,验证方法是否被调用:

-(void)captureOutput:(AVCaptureOutput *)output
didOutputSampleBuffer:(CMSampleBufferRef)buffer
       fromConnection:(AVCaptureConnection *)connection
{
    NSLog(@"captureOutput已触发,当前count: %d", _count);
    // 原有代码逻辑
}

关键注意事项

  • 相机权限是核心前提,未授权时会话不会产生任何帧输出
  • AVCaptureSession启动为异步操作,禁止在startRunning后同步阻塞等待帧
  • 全局变量易引发内存管理问题,优先使用实例变量

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 15:35:05