Задержка кадров виртуального дисплея Android

Основная проблема, которую я пытаюсь решить, - задержка примерно на секунду того, что отправляется на виртуальный дисплей. В общем, я пытаюсь сдвинуть все кадры на 1 секунду после начальной записи. Обратите внимание, что поверхность используется как вход, а другая поверхность используется как выход через этот виртуальный дисплей. Моя первоначальная идея - изучить несколько идей, учитывая, что модификация фреймворка Android или использование непубличных API - это хорошо. Ява или родной C/C++ в порядке.

а) Я попытался задержать кадры, размещенные на виртуальном дисплее или выходной поверхности, на секунду или две в SurfaceFlinger. Это не работает, поскольку вызывает задержку всех поверхностей на одинаковое время (синхронная обработка кадров).

b) MediaCodec использует поверхность в качестве входа для кодирования, а затем создает декодированные данные. Есть ли способ использовать MediaCodec таким образом, чтобы он фактически не кодировал, а создавал только некодированные необработанные кадры? Кажется маловероятным Кроме того, как MediaCodec делает это под капотом? Обрабатывайте вещи кадр за кадром. Если бы я мог экстраполировать метод, я мог бы извлечь кадр за кадром из моей входной поверхности и создать кольцевой буфер, задержанный на количество времени, которое мне требуется.

в) Как программные декодеры, такие как FFmpeg, на самом деле делают это в Android? Я предполагаю, что они занимают поверхность, но как бы они экстраполировали и обрабатывали кадр за кадром?

Обратите внимание, что я, конечно, могу кодировать и декодировать, чтобы получать кадры и публиковать их, но я хочу избежать фактического декодирования. Обратите внимание, что изменение структуры Android или использование непубличных API - это хорошо.

Я также нашел это: Получение кадра из SurfaceView

Кажется, что вариант d) может использовать SurfaceTexture, но я хотел бы избежать процесса кодирования / декодирования.

1 ответ

Решение

Насколько я понимаю, у вас есть виртуальный дисплей, который отправляет свои данные на поверхность. Если вы просто используете SurfaceView для вывода, кадры, выводимые виртуальным дисплеем, немедленно появляются на физическом дисплее. Цель состоит в том, чтобы ввести задержку в одну секунду между тем, когда виртуальный дисплей генерирует кадр, и когда получатель Surface получает его, чтобы (снова используя SurfaceView в качестве примера) физический дисплей отображал все с опозданием в секунду.

Основная концепция достаточно проста: отправьте вывод виртуального дисплея в SurfaceTexture и сохраните кадр в кольцевой буфер; тем временем другой поток читает кадры из хвостовой части кругового буфера и отображает их. Проблема в том, что @AdrianCrețu указал в комментариях: одна секунда данных экрана с полным разрешением при 60fps будет занимать значительную часть памяти устройства. Не говоря уже о том, что копирование такого большого количества данных будет довольно дорогим, и некоторые устройства могут не справиться.

(Неважно, делаете ли вы это в приложении или в SurfaceFlinger... данные до 60 кадров размером с экран должны храниться где-то целую секунду.)

Вы можете уменьшить объем данных различными способами:

  • Уменьшите разрешение. Масштабирование от 2560x1600 до 1280x800 удаляет 3/4 пикселей. На большинстве дисплеев трудно заметить потерю качества, но это зависит от того, что вы просматриваете.
  • Уменьшите глубину цвета. Переключение с ARGB8888 на RGB565 сократит размер в два раза. Это будет заметно, хотя.
  • Уменьшите частоту кадров. Вы создаете кадры для виртуального дисплея, поэтому вы можете обновлять его медленнее. Анимация все еще достаточно плавная при 30 кадрах в секунду, что вдвое снижает требования к памяти.
  • Примените сжатие изображения, например, PNG или JPEG. Довольно эффективно, но слишком медленно без поддержки оборудования.
  • Кодировать межкадровые различия. Если от кадра к кадру мало что меняется, инкрементные изменения могут быть очень маленькими. Технологии настольного зеркалирования, такие как VNC, делают это. Несколько медленнее делать в программном обеспечении.

Видеокодек, такой как AVC, будет сжимать кадры и кодировать межкадровые различия. Вот так вы получаете 1 Гбайт / с до 10 Мбит / с и при этом все равно выглядите неплохо.

Рассмотрим, например, пример "непрерывного захвата" в Grafika. Он подает выходные данные камеры в кодировщик MediaCodec и сохраняет выходные данные в кодировке H.264 в кольцевом буфере. Когда вы нажимаете "захват", это экономит последние 7 секунд. Это может так же легко воспроизвести видео с камеры с задержкой в ​​7 секунд, и для этого требуется всего несколько мегабайт памяти.

Команда "screenrecord" может выводить выходные данные H.264 или необработанные кадры через соединение ADB, хотя на практике ADB недостаточно быстр, чтобы не отставать от необработанных кадров (даже на крошечных дисплеях). Он не делает ничего, что вы не можете сделать из приложения (теперь, когда у нас есть API медиапроекции), поэтому я бы не рекомендовал использовать его в качестве примера кода.

Если вы еще этого не сделали, может быть полезно прочитать документацию по графической архитектуре.

Другие вопросы по тегам