Ответ 1
Отличный вопрос.
Быстрый бит фона для всех, кто читает это:
Цель состоит в том, чтобы минимизировать задержку отображения, то есть время между тем, когда приложение отображает кадр, и когда панель дисплея загорается пикселями. Если вы просто бросаете контент на экран, это не имеет значения, потому что пользователь не может сказать разницы. Если вы отвечаете на сенсорный ввод, однако, каждый латентный период заставляет ваше приложение чувствовать себя немного менее отзывчивым.
Проблема похожа на A/V sync, где вам нужен звук, связанный с кадром, чтобы выйти из динамика, когда видеокадра отображается на экране. В этом случае общая латентность не имеет значения до тех пор, пока она будет одинаково одинаковой на аудио и видео выходах. Это сталкивается с очень похожими проблемами, потому что вы потеряете синхронизацию, если SurfaceFlinger закроется, и ваше видео будет последовательно отображаться на один кадр позже.
SurfaceFlinger работает с повышенным приоритетом и делает относительно небольшую работу, поэтому вряд ли может пропустить бит самостоятельно... но это может произойти. Кроме того, он компонует кадры из нескольких источников, некоторые из которых используют заграждения для сигнализации асинхронного завершения. Если видеофрагмент во время записи формируется с выходом OpenGL, а рендеринг GLES не завершен, когда достигнут крайний срок, вся композиция будет отложена до следующего VSYNC.
Желание свести к минимуму задержку было достаточно сильным, так что в выпуске Android KitKat (4.4) появилась функция "DispSync" в SurfaceFlinger, которая бреет половину латентного времени от обычной двухкадровой задержки. (Это кратко упоминается в документе графической архитектуры, но не широко используется.)Так что ситуация.. В прошлом это было меньше проблем для видео, потому что 30fps-видео обновляет каждый другой фрейм. Икоты работают сами по себе, потому что мы не пытаемся сохранить очередь в полном объеме. Мы начинаем видеть видео с частотой 48 Гц и 60 Гц, поэтому это имеет значение больше.
Вопрос заключается в том, как мы обнаруживаем, что кадры, которые мы отправляем в SurfaceFlinger, отображаются как можно скорее или тратят лишний кадр, ожидающий буфер, который мы отправили ранее?
Первая часть ответа: вы не можете. На SurfaceFlinger нет запроса статуса или обратного вызова, который расскажет вам, что это за состояние. Теоретически вы можете запросить сам BufferQueue, но это не обязательно скажет вам, что вам нужно знать.
Проблема с запросами и обратными вызовами заключается в том, что они не могут сказать вам, что такое состояние, только то, что было в состоянии. К тому моменту, когда приложение получает информацию и действует на нее, ситуация может быть совершенно иной. Приложение будет работать с нормальным приоритетом, поэтому оно будет задерживаться.
Для A/V-синхронизации это немного сложнее, потому что приложение не может знать характеристики дисплея. Например, некоторые дисплеи имеют "интеллектуальные панели", в которые встроена память. (Если то, что на экране не обновляется часто, вы можете сэкономить много энергии, не имея панели сканирования пикселей по шине памяти 60x в секунду.) Они могут добавить дополнительный кадр задержки, который должен быть учтен.
Решение Android движется в направлении A/V sync, чтобы приложение указывало SurfaceFlinger, когда он хочет, чтобы кадр отображался. Если SurfaceFlinger пропустит крайний срок, он опустит рамку. Это было добавлено экспериментально в 4.4, хотя оно на самом деле не предназначено для использования до следующего выпуска (оно должно работать достаточно хорошо в "предварительном просмотре L", хотя я не знаю, включает ли это все части, необходимые для его полного использования).
Способ использования приложения заключается в вызове расширения eglPresentationTimeANDROID()
до eglSwapBuffers()
. Аргументом функции является желаемое время представления в наносекундах, используя ту же самую временную базу, что и хореограф (в частности, Linux CLOCK_MONOTONIC
). Поэтому для каждого кадра вы берете временную метку, которую вы получили от хореографа, добавляете необходимое количество кадров, умноженное на приблизительную частоту обновления (которую вы можете получить, запросив объект Display - см. MiscUtils # getDisplayRefreshNsec()) и передать его EGL. При смене буферов требуемое время представления передается вместе с буфером.
Вспомните, что SurfaceFlinger просыпается один раз в VSYNC, просматривает коллекцию ожидающих буферов и доставляет набор аппаратных средств отображения через Hardware Composer. Если вы запросите отображение в момент времени T, и SurfaceFlinger считает, что кадр, переданный на аппаратное обеспечение дисплея, будет отображаться в момент времени T-1 или ранее, кадр будет удерживаться (и предыдущий кадр повторно показан). Если рамка появится в момент времени T, она будет отправлена на дисплей. Если кадр появится в момент времени T + 1 или более поздний (т.е. Он пропустит свой крайний срок), и еще один кадр за ним в очереди, которая запланирована на более позднее время (например, кадр, предназначенный для времени T + 1), затем рамка, предназначенная для времени T, будет отброшена.
Решение не идеально подходит для вашей проблемы. Для синхронизации A/V вам требуется постоянная латентность, а не минимальная латентность. Если вы посмотрите на графику "запланированный своп, вы можете найти код, который использует eglPresentationTimeANDROID()
способом, аналогичным тому, что сделает видеоплеер, (В текущем состоянии это немного больше, чем "генератор тона" для создания выхода systrace, но здесь есть основные части.) Стратегия состоит в том, чтобы сделать несколько кадров впереди, поэтому SurfaceFlinger никогда не иссякает, но это точно неправильно для вашего приложение.
Механизм представления-времени, однако, обеспечивает способ удаления кадров, а не для их резервного копирования. Если вам известно, что между временем, сообщенным хореографом, и временем, когда отображается ваш кадр, есть два кадра латентности, вы можете использовать эту функцию, чтобы гарантировать, что кадры будут отброшены, а не поставлены в очередь, если они находятся слишком далеко от мимо. Активность Grafika позволяет вам установить частоту кадров и запрошенную задержку, а затем просмотреть результаты в систебре.
Было бы полезно, чтобы приложение узнало, сколько на самом деле имеет количество задержек SurfaceFlinger, но для этого нет запроса. (В любом случае это неловко, так как "интеллектуальные панели" могут изменять режимы, тем самым изменяя латентность отображения, но если вы не работаете с A/V sync, все, что вам действительно нужно, сводит к минимуму задержку SurfaceFlinger.) достаточно безопасно принимать два кадра на 4.3+. Если это не два кадра, у вас может быть субоптимальная производительность, но чистый эффект будет не хуже, чем вы могли бы получить, если бы вы вообще не устанавливали время презентации.
Вы можете попробовать установить желаемое время показа, равное временной отметке хореографа; временная метка в недавнем прошлом означает "показать как можно скорее". Это обеспечивает минимальную задержку, но может иметь неприятные последствия при плавности. SurfaceFlinger имеет двухкадровую задержку, потому что он дает все в системе достаточно времени для выполнения работы. Если ваша рабочая нагрузка неравномерна, вы будете колебаться между однокадровой и двухкадровой задержкой, и выход будет выглядеть зависает на переходах. (Это было проблемой для DispSync, что сокращает общее время до 1,5 кадров.)
Я не помню, когда была добавлена функция eglPresentationTimeANDROID()
, но в старых версиях она должна быть не-op.
Нижняя строка: для "L" и в некоторой степени 4.4 вы должны иметь возможность получить нужное поведение, используя расширение EGL с двумя кадрами задержки. В более ранних версиях нет помощи от системы. Если вы хотите убедиться, что на вашем пути нет буфера, вы можете преднамеренно отбросить кадр так часто, чтобы освободить буферную очередь.
Обновить: один из способов избежать очередей кадров - вызвать eglSwapInterval(0)
. Если вы отправляете вывод непосредственно на дисплей, вызов будет отключать синхронизацию с VSYNC, что не позволяет ограничить частоту кадров приложения. При рендеринге через SurfaceFlinger это приводит к тому, что BufferQueue переходит в "режим async", что заставляет его отбрасывать кадры, если они отправляются быстрее, чем система может их отображать.
Обратите внимание, что вы по-прежнему трижды буферизированы: отображается один буфер, один из которых удерживается SurfaceFlinger, который будет отображаться на следующем флип, и один втягивается приложением.