Подтвердить что ты не робот

AVCaptureDeviceOutput не вызывает метод делегата captureOutput

Я создаю приложение iOS (мое первое), которое обрабатывает видео по-прежнему на лету. Чтобы погрузиться в это, я последовал примеру из документации AV * от Apple.

Процесс включает в себя настройку входа (камеры) и выхода. Выход работает с делегатом, который в этом случае является самим контроллером (он соответствует и реализует необходимый метод).

Проблема, с которой я столкнулась, заключается в том, что метод делегата никогда не вызван. Код ниже - это реализация контроллера, и в нем есть несколько NSLogs. Я могу увидеть сообщение "запущено", но "метод делегата, который называется" никогда не отображается.

Этот код находится в пределах контроллера, который реализует протокол AVCaptureVideoDataOutputSampleBufferDelegate.

- (void)viewDidLoad {

    [super viewDidLoad];

    // Initialize AV session    
        AVCaptureSession *session = [AVCaptureSession new];

        if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
            [session setSessionPreset:AVCaptureSessionPreset640x480];
        else
            [session setSessionPreset:AVCaptureSessionPresetPhoto];

    // Initialize back camera input
        AVCaptureDevice *camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

        NSError *error = nil;

        AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:camera error:&error];

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


    // Initialize image output
        AVCaptureVideoDataOutput *output = [AVCaptureVideoDataOutput new];

        NSDictionary *rgbOutputSettings = [NSDictionary dictionaryWithObject:
                                           [NSNumber numberWithInt:kCMPixelFormat_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey];
        [output setVideoSettings:rgbOutputSettings];
        [output setAlwaysDiscardsLateVideoFrames:YES]; // discard if the data output queue is blocked (as we process the still image)


        //[output addObserver:self forKeyPath:@"capturingStillImage" options:NSKeyValueObservingOptionNew context:@"AVCaptureStillImageIsCapturingStillImageContext"];

        videoDataOutputQueue = dispatch_queue_create("VideoDataOutputQueue", DISPATCH_QUEUE_SERIAL);
        [output setSampleBufferDelegate:self queue:videoDataOutputQueue];


        if( [session canAddOutput:output] ){
            [session addOutput:output];
        }

        [[output connectionWithMediaType:AVMediaTypeVideo] setEnabled:YES];


    [session startRunning];

    NSLog(@"started");


}


- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {

        NSLog(@"delegate method called");

        CGImageRef cgImage = [self imageFromSampleBuffer:sampleBuffer];

        self.theImage.image = [UIImage imageWithCGImage: cgImage ];

        CGImageRelease( cgImage );

}

Примечание. Я создаю iOS 5.0 как цель.

Edit:

Я нашел question, который, хотя и просит решение другой проблемы, делает именно то, что должен делать мой код. Я скопировал код из этого вопроса дословно в пустое приложение xcode, добавил NSLogs к функции captureOutput и не получил вызов. Это проблема конфигурации? Что-то мне не хватает?

4b9b3361

Ответ 1

Ваша session - локальная переменная. Его объем ограничен viewDidLoad. Поскольку это новый проект, я с уверенностью могу сказать, что вы используете ARC. В этом случае этот объект не будет протекать и, следовательно, продолжит жить так, как это было бы в связанном вопросе, скорее компилятор обеспечит освобождение объекта до выхода viewDidLoad.

Следовательно, ваш сеанс не запущен, потому что он больше не существует.

(в стороне: self.theImage.image = ... является небезопасным, так как он выполняет действие UIKit основной очереди, вы, вероятно, хотите dispatch_async, что до dispatch_get_main_queue())

Итак, выборочные поправки:

@implementation YourViewController
{
     AVCaptureSession *session;
}

- (void)viewDidLoad {

    [super viewDidLoad];

    // Initialize AV session    
        session = [AVCaptureSession new];

        if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
            [session setSessionPreset:AVCaptureSessionPreset640x480];
        else
         /* ... etc ... */
}


- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {

        NSLog(@"delegate method called");

        CGImageRef cgImage = [self imageFromSampleBuffer:sampleBuffer];

        dispatch_sync(dispatch_get_main_queue(),
        ^{
            self.theImage.image = [UIImage imageWithCGImage: cgImage ];
            CGImageRelease( cgImage );
         });
}

Большинство людей выступают за использование подчеркивания в начале имен переменных экземпляра в настоящее время, но я пропустил его для простоты. Вы можете использовать Xcode, встроенный в инструмент refactor, чтобы исправить это после того, как вы подтвердили правильность диагноза.

Я переместил CGImageRelease внутри блока, отправленного в основную очередь, чтобы гарантировать, что его время жизни выходит за пределы его захвата в UIImage. Я не могу сразу найти документацию, подтверждающую, что объекты CoreFoundation имеют срок службы, автоматически расширяемый при захвате в блоке.

Ответ 2

Я нашел еще одну причину, по которой метод делегата didOutputSampleBuffer не может быть вызван - сохранение в файл и получение выходных соединений образца буфера являются взаимоисключающими. Другими словами, если ваш сеанс уже имеет AVCaptureMovieFileOutput, а затем вы добавляете AVCaptureVideoDataOutput, вызываются только методы делегата AVCaptureFileOutputRecordingDelegate.

Только для справки я не смог найти нигде в документации по дизайну AV Foundation явное описание этого ограничения, но поддержка Apple подтвердила это несколько лет назад, как отмечено в этом SO ответьте.

Один из способов решения проблемы - удалить AVCaptureMovieFileOutput полностью и вручную записать записанные кадры в файл в методе делегата didOutputSampleBuffer, наряду с обработкой пользовательских буферов. Вы можете найти эти два. Полезные ответы.

Ответ 3

В моем случае проблема есть, потому что я называю

if ([_session canAddOutput:_videoDataOutput])
        [_session addOutput:_videoDataOutput];

прежде чем я позвоню

[_session startRunning];

Я только начинаю звонить addOutput: после startRunning

Надеюсь, что это поможет кому-то.