Настройка GCD планирования/приоритета между потоками

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

Вот несколько логов для пояснений:

captureOutput(): used time:  0.027741014957428  , frame rate:  36.0477077545513
captureOutput(): used time:  0.0285720229148865  , frame rate:  34.9992719444091
captureOutput(): used time:  0.0310209989547729  , frame rate:  32.2362281581567
captureOutput(): used time:  0.0268059968948364  , frame rate:  37.3050852733863
captureOutput(): used time:  0.0263729691505432  , frame rate:  37.9176115624965
motionCallback(): starting drawing: 
captureOutput(): used time:  0.0376390218734741  , frame rate:  26.5681718127947
motionCallback(): elapesed time:  0.0835540294647217
captureOutput(): used time:  0.0581380128860474  , frame rate:  17.2004502795793
captureOutput(): used time:  0.0364410281181335  , frame rate:  27.4415967836645
captureOutput(): used time:  0.0278580188751221  , frame rate:  35.8963070734734
captureOutput(): used time:  0.0283130407333374  , frame rate:  35.3194137435949
captureOutput(): used time:  0.0271909832954407  , frame rate:  36.7768972947616
captureOutput(): used time:  0.0268760323524475  , frame rate:  37.2078730552999

Как видите, у captureOutput частота кадров превышает 30 кадров в секунду. Как только motionCallback создает изображение, частота кадров падает, а затем снова увеличивается. Обратите внимание, что motionCallback() вызывается только каждый 15-й кадр захвата, поэтому средней вычислительной мощности должно быть достаточно.

CaptureOutput() работает с очередью, созданной следующим образом:

required init?(coder aDecoder: NSCoder) {
  super.init(coder: aDecoder)
  self.m_captureSessionQueue = DispatchQueue(label: "CaptureSessionOutputQueue", attributes: DispatchQueueAttributes.serial)
}

Позже я настроил AVCapture следующим образом:

videoDataOutput.setSampleBufferDelegate(self, queue: self.m_captureSessionQueue)

motionCallback() настроен следующим образом:

let interval = 0.4
if m_manager.isDeviceMotionAvailable {
  m_manager.showsDeviceMovementDisplay = true
  m_manager.deviceMotionUpdateInterval = interval
  // XArbitraryCorrectedZVertical
  m_manager.startDeviceMotionUpdates(using: CMAttitudeReferenceFrame.xArbitraryCorrectedZVertical , to: OperationQueue.main, withHandler: motionCallback)
  print("viewDidLoad(): CM initialized !")
}

и позже:

func motionCallback(_ motion: CMDeviceMotion?, error: NSError?) -> Void {
  guard let mot = motion else {return}
  print("motionCallback(): starting drawing: ")

  let start = NSDate() // <<<<<<<<<< Start time
  self.m_instrview?.update(attitude: mot.attitude)
  self.m_overlayImage = CIImage(image: self.m_instrview!.generateImage()!)

  let end = NSDate()  // <<<<<<<<<<   end time
  let timeInterval: Double = end.timeIntervalSince(start as Date)
  print("motionCallback(): elapesed time: ", timeInterval)
}

и generateImage() следующим образом:

func generateImage() -> UIImage {
  UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main().scale )
  drawHierarchy(in: self.bounds, afterScreenUpdates: true)
  let image = UIGraphicsGetImageFromCurrentImageContext()
  UIGraphicsEndImageContext()
  return image!
}

Идея состоит в том, чтобы каждый раз, когда система CoreMotion получает обновление, я создавал новое изображение.

Мой вопрос: как я могу усреднить время вычисления motionCallback(), чтобы он не потреблял пиковую мощность процессора, которая приводит к падению частоты кадров? Поэтому я бы согласился, чтобы он работал в 10 раз дольше, но потреблял бы только 1/10 процессора за это время.

Есть идеи, как это можно контролировать?

Спасибо Крис


person Chris    schedule 28.07.2016    source источник
comment
Ваш обратный вызов движения находится в основной очереди, которая включает вызов generateImage, который, по-видимому, является функцией, интенсивно использующей ЦП. Обратите внимание, что если вы отправляете это в фоновую очередь, вы все равно должны выполнять обновления пользовательского интерфейса в основной очереди.   -  person Paulw11    schedule 29.07.2016
comment
Да, это именно моя проблема, функция, интенсивно использующая ЦП, ДОЛЖНА вызываться в основном потоке. Итак, если я не могу понизить приоритет motionCallBack, как насчет повышения приоритета потока захвата? Как я могу это сделать ?   -  person Chris    schedule 29.07.2016
comment
Почему вы говорите, что функция интенсивного использования процессора должна вызываться в основном потоке? generateImage интенсивно использует процессор, но может работать в фоновом потоке. Как только это будет сделано, отправьте назначение CIImage в основной поток. Используйте поток с низким приоритетом для обработки основных событий движения.   -  person Paulw11    schedule 29.07.2016
comment
generateImage в основном содержит вызов UIView.drawHierarchy(), который является трудоемкой частью. Когда я вызываю UIView.drawHierarchy() вне основного потока, я не получаю никаких представлений, отображаемых в изображении. Поэтому я и написал, что ДОЛЖЕН быть вызван...   -  person Chris    schedule 29.07.2016
comment
Затем я предлагаю вам использовать инструмент профилирования времени, чтобы увидеть, можете ли вы определить конкретную проблему, в противном случае поделитесь кодом, который не работает.   -  person Paulw11    schedule 29.07.2016


Ответы (1)


Создайте асинхронную очередь (используя dispatch_queue_create) с более низким приоритетом, чем та, которая захватывает ваши кадры. Запустите свой motionCallback в этой очереди с более низким приоритетом.

У вас есть метод motionCapture, работающий в основной очереди, который выполняет свои задачи в основном потоке. Не делай этого.

Эта строка неверна:

m_manager.startDeviceMotionUpdates(
  using: CMAttitudeReferenceFrame.xArbitraryCorrectedZVertical , 
  to: OperationQueue.main, 
  withHandler: motionCallback)

Измените параметр на:.

Вам нужно создать новую последовательную очередь, которая выполняется с более низким приоритетом, чем созданная вами CaptureSessionOutputQueue, и передать ее в параметре to: выше.

Я только начинаю изучать Swift 3, поэтому у меня пока нет синтаксиса нового кода для создания очередей отправки.

person Duncan C    schedule 28.07.2016
comment
что я уже сделал, так это поместил этот код в DispatchQueue.global(attributes: .qosBackground).async { ... }, но это привело к зависанию графического интерфейса. Может ли drawHierarchy вызываться в основном потоке? - person Chris; 28.07.2016
comment
Поэтому отредактируйте свой вопрос, включив в него сведения о настройке вашей очереди, а также соответствующие части кода, в которых работают обе очереди. - person Duncan C; 28.07.2016
comment
Да, вызов, который рисует изображения в контексте, почти наверняка должен быть вызван в основном потоке. - person Duncan C; 29.07.2016
comment
Отдельная очередь не решила проблему. Я получаю сбой, говоря, что он должен быть вызван в основном потоке. Таким образом, разделение в другую очередь не вариант. Любые дальнейшие идеи? Как насчет увеличения приоритета очереди захвата? Как это сделать в DGC/Swift 3? - person Chris; 29.07.2016