Применение эффекта к видео предварительного просмотра с камеры iPhone

Моя цель — написать собственный контроллер просмотра камеры, который:

  1. Можно делать фотографии во всех четырех ориентациях интерфейса как на заднюю, так и на переднюю камеру, если она доступна.
  2. Правильно поворачивает и масштабирует превью «видео», а также фото в полном разрешении.
  3. Позволяет применять (простой) эффект ОБА к предварительному просмотру «видео» и фотографии с полным разрешением.

Реализация (на iOS 4.2/Xcode 3.2.5):

Из-за требования (3) мне нужно было перейти к AVFoundation.

Я начал с Технических вопросов и ответов QA1702 и внес следующие изменения:

  1. Изменен sessionPreset на AVCaptureSessionPresetPhoto.
  2. Добавлен AVCaptureStillImageOutput в качестве дополнительного вывода перед запуском сеанса.

Проблема, с которой я сталкиваюсь, связана с производительностью обработки изображения предварительного просмотра (кадр предварительного просмотра «видео»).

Во-первых, я получаю результат UIImage imageFromSampleBuffer: в образце буфера из captureOutput:didOutputSampleBuffer:fromConnection:. Затем я масштабирую и поворачиваю его для экрана, используя CGGraphicsContext.

На данный момент частота кадров уже ниже 15 кадров в секунду, которые указаны в видеовыходе сеанса, и когда я добавляю эффект, она падает ниже или около 10. Приложение быстро падает из-за нехватки памяти.

Мне удалось снизить частоту кадров до 9 кадров в секунду на iPhone 4 и до 8 кадров в секунду на iPod Touch (4-го поколения).

Я также добавил некоторый код для «очистки» очереди отправки, но я не уверен, насколько это на самом деле помогает. По сути, каждые 8-10 кадров устанавливается флаг, который сигнализирует captureOutput:didOutputSampleBuffer:fromConnection: о немедленном возврате, а не об обработке кадра. Флаг сбрасывается после завершения операции синхронизации в выходной очереди диспетчеризации.

На данный момент я даже не возражаю против низкой частоты кадров, но очевидно, что мы не можем выпускать игру с нехваткой памяти. Кто-нибудь знает, как принять меры, чтобы предотвратить нехватку памяти в этом случае (и/или лучший способ «сбросить» очередь отправки)?


person gerry3    schedule 04.02.2011    source источник


Ответы (2)


Чтобы предотвратить проблемы с памятью, просто создайте пул автоматического освобождения в captureOutput:didOutputSampleBuffer:fromConnection:.

Это имеет смысл, поскольку imageFromSampleBuffer: возвращает автоматически выпущенный объект UIImage. Кроме того, он сразу же освобождает любые автоматически выпущенные объекты, созданные кодом обработки изображений.

// Delegate routine that is called when a sample buffer was written
- (void)captureOutput:(AVCaptureOutput *)captureOutput 
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer 
fromConnection:(AVCaptureConnection *)connection
{ 
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    // Create a UIImage from the sample buffer data
    UIImage *image = [self imageFromSampleBuffer:sampleBuffer];

    < Add your code here that uses the image >

    [pool release];
}

Мое тестирование показало, что это будет работать без предупреждений о памяти на iPhone 4 или iPod Touch (4-го поколения), даже если запрошенный FPS очень высок (например, 60) и обработка изображения очень медленная (например, 0,5+ секунды).

СТАРОЕ РЕШЕНИЕ:

Как отметил Брэд, Apple рекомендует выполнять обработку изображений в фоновом режиме, чтобы не мешать отзывчивости пользовательского интерфейса. Я не заметил большого отставания в этом случае, но лучшие практики есть лучшие практики, поэтому используйте приведенное выше решение с пулом автоматического выпуска вместо того, чтобы запускать его в основной очереди отправки/основном потоке.

Чтобы предотвратить проблемы с памятью, просто используйте основную очередь отправки вместо создания новой.

Это также означает, что вам не нужно переключаться на основной поток в captureOutput:didOutputSampleBuffer:fromConnection:, когда вы хотите обновить пользовательский интерфейс.

В setupCaptureSession измените ОТ:

// Configure your output.
dispatch_queue_t queue = dispatch_queue_create("myQueue", NULL);
[output setSampleBufferDelegate:self queue:queue];
dispatch_release(queue);

TO:

// we want our dispatch to be on the main thread
[output setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
person gerry3    schedule 04.02.2011
comment
Да, я предполагаю, что это приводит к тому, что сеанс просто пропускает кадры, когда что-то не успевает. Однако я считаю, что Apple не одобряет это из-за того, что это может повлиять на отзывчивость интерфейса (из-за обработки, которую вы выполняете в основном потоке). По-прежнему кажется, что должен быть способ обрабатывать кадры в неосновной очереди без перегрузки. - person Brad Larson; 05.02.2011
comment
Что ж, на самом деле пока он достаточно отзывчив, но я мог бы попытаться запустить фактическую обработку в фоновом режиме после captureOutput:didOutputSampleBuffer:fromConnection:, а затем вернуться к основному потоку, чтобы обновить пользовательский интерфейс. Этот подход действительно решает проблему, и это то, что делает пример Apple GLVideoFrame с WWDC (конечно, обработка OpenGL очень быстрая). - person gerry3; 05.02.2011
comment
Выполнение обработки в фоновом режиме снизило частоту кадров до неприемлемого уровня и сделало приложение значительно менее отзывчивым, даже если пользовательский интерфейс был немного более отзывчивым. Выполнение всего в основном потоке кажется лучшим подходом в этом случае. - person gerry3; 07.02.2011
comment
На самом деле, простое добавление автовыпуска, кажется, решило проблемы с памятью, позволяя обработке оставаться в фоновой очереди/потоке. Я обновил ответ. - person gerry3; 08.02.2011
comment
@ gerry3 - Вы тогда видели на консоли ошибки об отсутствующем пуле автовыпуска или вам нужно было чаще сливать пул? - person Brad Larson; 08.02.2011
comment
@Брэд Последний. Не уверен, почему вызывающая сторона не истощает пул каждый кадр. - person gerry3; 08.02.2011

Фундаментально лучшим подходом было бы использовать OpenGL для обработки как можно большей тяжелой работы, связанной с изображениями (как я вижу, вы пытаетесь в ваша последняя попытка). Однако даже в этом случае у вас могут возникнуть проблемы с созданием кадров для обработки.

Хотя кажется странным, что вы столкнетесь с накоплением памяти при обработке кадров (по моему опыту, вы просто перестанете их получать, если не сможете обработать их достаточно быстро), очереди Grand Central Dispatch могут быть забиты, если они ожидают ввод/вывод.

Возможно, диспетчерский семафор позволит вам ограничить добавление новых элементов в очереди обработки. Чтобы узнать больше об этом, я настоятельно рекомендую Майка Эша "GCD Practicum», в которой он рассматривает оптимизацию операции обработки эскизов, привязанной к вводу-выводу, с использованием диспетчерских семафоров.

person Brad Larson    schedule 04.02.2011
comment
OpenGL обеспечивал отличную частоту кадров, но не при правильной предустановке (хотя видеокадры предустановки Photo имеют лишь немного более высокое разрешение, чем в примере). Да, странно, но легко воспроизвести. Вы можете взять пример кода Apple, добавить его в новое приложение для iPhone, добавить короткую задержку в captureOutput:didOutputSampleBuffer:fromConnection: и посмотреть, как он рухнет за считанные секунды. Одно различие между этим и примером OpenGL, который я еще не исследовал, заключается в том, что последний использовал основную очередь диспетчеризации (главный поток), которая в конечном счете может быть похожа на семафорный подход. Я буду изучать оба. - person gerry3; 04.02.2011
comment
Это оказалось так же просто, как использовать основную очередь отправки (см. мой ответ). - person gerry3; 05.02.2011
comment
До сих пор лучшим решением было добавление пула авторелиза. Я обновил свой ответ. - person gerry3; 08.02.2011