【问题标题】:iOS: captureOutput:didOutputSampleBuffer:fromConnection is NOT callediOS:未调用 captureOutput:didOutputSampleBuffer:fromConnection
【发布时间】:2014-09-26 10:01:28
【问题描述】:

我想从 AVCaptureSession 的实时提要中提取帧,并且我使用 Apple 的 AVCam 作为测试用例。这是 AVCam 的链接:

https://developer.apple.com/library/ios/samplecode/AVCam/Introduction/Intro.html

我发现 captureOutput:didOutputSampleBuffer:fromConnection 没有被调用,我想知道为什么或我做错了什么。

这是我所做的:

(1) 我让AVCamViewController 成为代表

@interface AVCamViewController () <AVCaptureFileOutputRecordingDelegate, AVCaptureVideoDataOutputSampleBufferDelegate>

(2) 我创建了一个AVCaptureVideoDataOutput 对象并将其添加到会话中

AVCaptureVideoDataOutput *videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
if ([session canAddOutput:videoDataOutput])
     {
         [session addOutput:videoDataOutput];
     }

(3) 我添加了委托方法并通过记录一个随机字符串进行测试

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    NSLog(@"I am called");

}

测试应用程序工作但 captureOutput:didOutputSampleBuffer:fromConnection 未被调用。

(4) 我在 SO 上读到 AVCaptureSession *session = [[AVCaptureSession alloc] init]; 中的会话变量在 viewDidLoad 中是本地的,这可能是未调用委托的可能原因,我将其设为 AVCamViewController 类的实例变量,但未调用它。

这是我正在测试的 viewDidLoad 方法(取自 AVCam),我在方法末尾添加了 AVCaptureDataOutput:

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Create the AVCaptureSession
    session = [[AVCaptureSession alloc] init];
    [self setSession:session];

    // Setup the preview view
    [[self previewView] setSession:session];

    // Check for device authorization
    [self checkDeviceAuthorizationStatus];

    // In general it is not safe to mutate an AVCaptureSession or any of its inputs, outputs, or connections from multiple threads at the same time.
    // Why not do all of this on the main queue?
    // -[AVCaptureSession startRunning] is a blocking call which can take a long time. We dispatch session setup to the sessionQueue so that the main queue isn't blocked (which keeps the UI responsive).

    dispatch_queue_t sessionQueue = dispatch_queue_create("session queue", DISPATCH_QUEUE_SERIAL);
    [self setSessionQueue:sessionQueue];

    dispatch_async(sessionQueue, ^{
        [self setBackgroundRecordingID:UIBackgroundTaskInvalid];

        NSError *error = nil;

        AVCaptureDevice *videoDevice = [AVCamViewController deviceWithMediaType:AVMediaTypeVideo preferringPosition:AVCaptureDevicePositionBack];
        AVCaptureDeviceInput *videoDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];

        if (error)
        {
            NSLog(@"%@", error);
        }

        if ([session canAddInput:videoDeviceInput])
        {
            [session addInput:videoDeviceInput];
            [self setVideoDeviceInput:videoDeviceInput];

            dispatch_async(dispatch_get_main_queue(), ^{
                // Why are we dispatching this to the main queue?
                // Because AVCaptureVideoPreviewLayer is the backing layer for AVCamPreviewView and UIView can only be manipulated on main thread.
                // Note: As an exception to the above rule, it is not necessary to serialize video orientation changes on the AVCaptureVideoPreviewLayer’s connection with other session manipulation.

                [[(AVCaptureVideoPreviewLayer *)[[self previewView] layer] connection] setVideoOrientation:(AVCaptureVideoOrientation)[self interfaceOrientation]];
            });
        }

        AVCaptureDevice *audioDevice = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
        AVCaptureDeviceInput *audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error];

        if (error)
        {
            NSLog(@"%@", error);
        }

        if ([session canAddInput:audioDeviceInput])
        {
            [session addInput:audioDeviceInput];
        }

        AVCaptureMovieFileOutput *movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
        if ([session canAddOutput:movieFileOutput])
        {
            [session addOutput:movieFileOutput];
            AVCaptureConnection *connection = [movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
            if ([connection isVideoStabilizationSupported])
                [connection setEnablesVideoStabilizationWhenAvailable:YES];
            [self setMovieFileOutput:movieFileOutput];
        }

        AVCaptureStillImageOutput *stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
        if ([session canAddOutput:stillImageOutput])
        {
            [stillImageOutput setOutputSettings:@{AVVideoCodecKey : AVVideoCodecJPEG}];
            [session addOutput:stillImageOutput];
            [self setStillImageOutput:stillImageOutput];
        }

        AVCaptureVideoDataOutput *videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
        [videoDataOutput setSampleBufferDelegate:self queue:sessionQueue];

        if ([session canAddOutput:videoDataOutput])
        {
            NSLog(@"Yes I can add it");
            [session addOutput:videoDataOutput];
        }

    });
}

- (void)viewWillAppear:(BOOL)animated
{
    dispatch_async([self sessionQueue], ^{
        [self addObserver:self forKeyPath:@"sessionRunningAndDeviceAuthorized" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:SessionRunningAndDeviceAuthorizedContext];
        [self addObserver:self forKeyPath:@"stillImageOutput.capturingStillImage" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:CapturingStillImageContext];
        [self addObserver:self forKeyPath:@"movieFileOutput.recording" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:RecordingContext];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(subjectAreaDidChange:) name:AVCaptureDeviceSubjectAreaDidChangeNotification object:[[self videoDeviceInput] device]];

        __weak AVCamViewController *weakSelf = self;
        [self setRuntimeErrorHandlingObserver:[[NSNotificationCenter defaultCenter] addObserverForName:AVCaptureSessionRuntimeErrorNotification object:[self session] queue:nil usingBlock:^(NSNotification *note) {
            AVCamViewController *strongSelf = weakSelf;
            dispatch_async([strongSelf sessionQueue], ^{
                // Manually restarting the session since it must have been stopped due to an error.
                [[strongSelf session] startRunning];
                [[strongSelf recordButton] setTitle:NSLocalizedString(@"Record", @"Recording button record title") forState:UIControlStateNormal];
            });
        }]];
        [[self session] startRunning];
    });
}

谁能告诉我为什么以及如何解决它的建议?

【问题讨论】:

  • OP 你能解决这个问题吗?我也有类似的问题。
  • 您使用的是 iOS 8 吗?
  • OP 你应该接受@Carl 的回答。

标签: ios objective-c avcapturesession avcapturedevice avcapture


【解决方案1】:

我已经对此进行了很多实验,我想我可能已经有了答案。我有类似但不同的代码,它们是从头开始编写的,而不是从 Apple 的示例中复制而来(现在有点旧了)。

我认为是部分......

AVCaptureMovieFileOutput *movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
    if ([session canAddOutput:movieFileOutput])
    {
        [session addOutput:movieFileOutput];
        AVCaptureConnection *connection = [movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
        if ([connection isVideoStabilizationSupported])
            [connection setEnablesVideoStabilizationWhenAvailable:YES];
        [self setMovieFileOutput:movieFileOutput];
    }

根据我的实验,这是导致您出现问题的原因。在我的代码中,没有调用 captureOutput:didOutputSampleBuffer:fromConnection 。我认为视频系统要么为您提供一系列样本缓冲区,要么将压缩、优化的电影文件记录到磁盘,而不是两者兼而有之。 (至少在 iOS 上。)我想这是有道理的/不足为奇,但我还没有在任何地方看到它的文档!

另外,在某一时刻,我似乎遇到了错误和/或当我打开麦克风时没有发生缓冲区回调。再次未记录,这些是错误 -11800(未知错误)。但我不能总是重现。

【讨论】:

  • 我刚刚遇到了这个确切的问题,这对我有用。为会话提供 AVCaptureMovieFileOutput 可防止调用 captureOutput:didOutputSampleBuffer:fromConnection。
【解决方案2】:

您的代码对我来说看起来不错,我可以想出 10 种您可以尝试的猜测和检查方法,因此我将采用一种不同的方法,有望间接解决该问题。除了我认为 AVCam 写得不好之外,我认为你最好看一个只关注实时视频而不是录制视频和拍摄静止图像的示例。我已经提供了一个示例,仅此而已。

-(void)startSession {
    self.session = [AVCaptureSession new];
    self.session.sessionPreset = AVCaptureSessionPresetMedium;
    AVCaptureDevice *backCamera;
    for (AVCaptureDevice *device in [AVCaptureDevice devices]) {
        if ([device hasMediaType:AVMediaTypeVideo] && device.position == AVCaptureDevicePositionBack) {
            backCamera = device;
            break;
        }
    }
    NSError *error = nil;
    AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:backCamera error:&error];
    if (error) {
        // handle error
    }
    if ([self.session canAddInput:input]) {
        [self.session addInput:input];
    }
    AVCaptureVideoDataOutput *output = [AVCaptureVideoDataOutput new];
    [output setSampleBufferDelegate:self queue:self.queue];
    output.videoSettings = @{(id)kCVPixelBufferPixelFormatTypeKey:@(kCVPixelFormatType_32BGRA)};
    if ([self.session canAddOutput:output]) {
        [self.session addOutput:output];
    }
    dispatch_async(self.queue, ^{
        [self.session startRunning];
    });
}

【讨论】:

  • 我按照建议添加了它,但它仍然没有被调用:[videoDataOutput setSampleBufferDelegate:self queue:sessionQueue]; sessionQueue 被用于 AVCaptureSession 的所有 AV 输入和输出使用
  • 你能把你所有的代码贴在你设置会话的地方吗?
  • 是的,我添加了完整的viewDidLoad方法
  • @Paulo 你开始会议了吗? [会话开始运行]
  • 是的,在示例代码中的 ViewWillAppear 中调用了它,它运行良好,录制视频。我现在也添加了代码。
【解决方案3】:

当我在 React-Native 和原生 iOS/Swif/ObjectiveC 之间搭建桥梁时,我遇到了同样的问题。

然后我发现了 2 个类似的问题。 @Carl 的回答似乎确实是正确的。 然后我找到了其他问题的答案:

我联系了 Apple 支持的工程师,他告诉我不支持同时使用 AVCaptureVideoDataOutput + AVCaptureMovieFileOutput。不知道他们以后会不会支持,但是他用了“暂时不支持”这个词。

我鼓励您像我一样 (bugreport.apple.com) 填写一份错误报告/功能请求,因为它们会衡量人们想要某样东西的难易程度,我们也许可以在不久的将来看到这一点。

Simultaneous AVCaptureVideoDataOutput and AVCaptureMovieFileOutput

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-08-12
    • 2015-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-25
    • 2017-03-08
    • 1970-01-01
    相关资源
    最近更新 更多