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
相关产品推荐
相关产品推荐

