Русский
Русский
English
Статистика
Реклама

Приложения для smart tv

Как портировать SDK Flutter на ТВ-приставку для разработки и запуска приложений Android TV

15.04.2021 16:11:54 | Автор: admin

Недавно мы успешно портировали фреймворк Flutter на ТВ-приставку c открытой программной платформой RDK. В этой статье расскажем о трудностях, с которыми пришлось столкнуться, и предложим решения для успешного запуска и повышения производительности.

Учитывая, что программный стек RDK или Reference Design Kit сейчас активно используется для разработки OTT-приложений, голосового управления приставками и других продвинутых функций для видео по запросу (VoD), мы хотели разобраться, сможет ли Flutter работать на ТВ-приставке. Оказалось, что да, но, как это обычно бывает, есть нюансы.

Далее мы по шагам распишем процесс портирования и запуска Flutter на встраиваемых Linux-платформах и разберемся, как этот SDK с открытым исходным кодом от Google чувствует себя на железе с ограниченными ресурсами и ARM-процессорами.

Но прежде чем переходить непосредственно к Flutter и его преимуществам скажем пару слов об исходном решении, которое было задействовано на ТВ-приставке. На плате работала связка набор библиотек EFL + протокол Wayland, а рисование примитивов было реализовано из node.js на основе плагинного нативного модуля. Это решение неплохо себя показало с точки зрения производительности при отображении кадров, однако сам EFL отнюдь не самый новый фреймворк для отрисовки. А в режиме выполнения node.js со своим огромным event-loopом казался уже не самой перспективной идеей. В то же время Flutter мог позволить нам задействовать более производительную связку рендеринга.

Для тех, кто не в теме: первую версию этого SDK с открытым кодом Google представил еще шесть лет назад. Тогда этот набор средств разработки годился только для Android. Сейчас на нем можно писать приложения для веба, iOS, Linux и даже Google Fuchsia. :-) Рабочий язык для разработки приложений на Flutter Dart, в свое время он был предложен в качестве альтернативы JavaScript.

Перед нами стоял вопрос: даст ли переход на Flutter какой-то выигрыш по производительности? Ведь подход там совершенно иной, хоть в конечном счете и имеется та же графическая подсистема Wayland + OpenGL. Ну и как там с поддержкой процессоров с neon-инструкциями? Были и другие вопросы, например, нюансы по переносу UI на dart или то, что поддержка Linux находится в стадии альфы-беты.

Сборка Flutter Engine для ТВ-приставок на базе ARM

Итак, начнем. Вначале Futter нужно запустить на чужеродной платформе с Wayland + OpenGL ES. В основе рендеринга у Flutter лежит библиотека Skia, которая прекрасно поддерживает OpenGL ES, поэтому в теории все выглядело хорошо.

При сборке Flutter под наши целевые устройства (три ТВ-приставки с RDK), к нашему удивлению, проблемы возникли только на одной. Не будем с ней сражаться, т.к. из-за старой архитектуре intel x86 она для нас не является приоритетной. Лучше сосредоточимся на оставшихся двух ARM-платформах.

Вот, с какими опциями мы собирали Flutter Engine:

./flutter/tools/gn \      --embedder-for-target \      --target-os linux \      --linux-cpu arm \      --target-sysroot DEVICE_SYSROOT      --disable-desktop-embeddings \      --arm-float-abi hard      --target-toolchain /usr      --target-triple arm-linux-gnueabihf      --runtime-mode debugninja -C out/linux_debug_unopt_arm

Большинство опций понятны: собираем под 32-битный ARM-процессор и Linux, выключая при этом все лишнее через --embedder-for-target --disable-desktop-embeddings.

Для сборки в системе должен быть установлен clang версии 9 и выше, т.е. это стандартный сборочный механизм Flutter, инструментарий кросс-компиляции gcc не пойдет. Самое важное подать корректный target-sysroot устройства с RDK.

Честно говоря, мы удивились, что при сборке не возникло вообще никаких нюансов. На выходе получаем заветную библиотеку flutter_engine.so и заголовок с необходимыми функциями для эмбеддера.

Теперь можно собрать целевой проект flutter/dart с нашей библиотекой/движком. Это сделать легко:

flutter --local-engine-src-path PATH_TO_BUILDED_ENGINE_src --local-engine=host_debug_unopt build bundle

Важно! Сборка проекта должна происходить не на устройстве с собранной библиотекой, а на хостовой, т.е. x86_64!

Для этого достаточно еще раз пройти путь сборкой gn и ninja только под x86_64! Именно она указывается в параметре host_debug_unopt.

PATH_TO_BUILDED_ENGINE_src это путь, где находится engine/src/out.

За запуск Flutter Engine под системой обычно отвечает embedder, именно он конфигурирует Flutter под целевую систему и дает основные контексты рендеринга библиотеке Skia и Dart-обработчику. Не так давно в состав Flutter добавили linux-embedder, и, в частности, GTK-embedder, так что можно воспользоваться им из коробки. На нашей платформе на момент портирования это был не вариант, нужно было что-то независимое от GTK.

Рассмотрим некоторые особенности реализации, которые пришлось учесть с кастомным эмбеддером (все, кто любит разбирать не нюансы, а исходники целиком, может сразу перейти к проекту нашего форка с доработками на github.com). К тому же по производительности наш вариант немного выигрывал у версии GTK, что было крайне важно для заказчика, и не тянул за собой весь зоопарк GTK-библиотек.

Так что же вообще нужно от эмбеддера для запуска flutter-приложения? Достаточно, чтобы он просто вызывал из библотеки flutter_engine.so

FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &args, display /* userdata */, &engine_);

где в качестве параметров идет передача настроек проекта (директория с собранным flutter bundle) FlutterProjectArgs args и аргументов рендеринга FlutterRendererConfig config.

В первой структуре как раз задается путь bundle-пакета, собранного flutter-утилитой, а во второй используются контексты OpenGL .

// пример использования на github.com/DEgITx/flutter_wayland/blob/master/src/flutter_application.cc

Все довольно примитивно, но этого достаточно для запуска приложения.

Проблемы и их решение

Теперь поговорим о нюансах, с которыми мы столкнулись на этапе портирования. А как же без них? Не только ведь библиотеки собирать :-)

1. Краш эмбеддера и замена очередности вызова функций

Первая проблема, с которой мы столкнулись краш эмбеддера под платформой. Казалось бы, инициализация egl-контекста в других приложения происходит нормально, FlutterRendererConfig инициализирован корректно, но нет эмбеддер не заводится. Значит в связке что-то явно не так. Оказалось, eglBindAPI нельзя вызывать перед eglGetDisplay, на котором происходит особая инициализация nexus-драйвера дисплея (у нас платформа базируется на чипе BCM). В обычном Linux это не проблема, но на целевой платформе оказалась иначе.

Корректная инициализация эмбеддера выглядит так:

egl_display_ = eglGetDisplay(display_);if (egl_display_ == EGL_NO_DISPLAY) {  LogLastEGLError();  FL_ERROR("Could not access EGL display.");  return false;}if (eglInitialize(egl_display_, nullptr, nullptr) != EGL_TRUE) {  LogLastEGLError();  FL_ERROR("Could not initialize EGL display.");  return false;}if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE) {  LogLastEGLError();  FL_ERROR("Could not bind the ES API.");  return false;}

// github.com/DEgITx/flutter_wayland/blob/master/src/wayland_display.cc корректная реализация, т.е. помогла измененная очередность вызова функций.

Теперь, когда нюанс запуска улажен, мы рады увидеть заветное демо-окно приложения на экране :-).

2. Оптимизация производительности

Настало время проверить производительность. И, честно говоря, она нас не сильно порадовала в режиме отладки (debug mode). Что-то работало шустро, что-то наоборот, имело большие просадки по фреймам и тормозило гораздо больше, чем что-то похожее на EFL+Node.js.

Мы немного расстроились и начали копать дальше. В SDK Flutter есть специальный режим компиляции машинного кода AOT, это даже не jit, а именно компиляция в нативный код со всеми сопутствующими оптимизациями, именно это подразумевается под по релиз-версией Flutter. Такой поддержки у нас в эмбеддере пока не было, добавляем.

Необходимы определенные инструкции, поданные аргументами к FlutterEngineRun

// полная реализация github.com/DEgITx/flutter_wayland/blob/master/src/elf.cc

vm_snapshot_instructions_ = dlsym(fd, "_kDartVmSnapshotInstructions");if (vm_snapshot_instructions_ == NULL) {  error_ = strerror(errno);  break;}vm_isolate_snapshot_instructions_ = dlsym(fd, "_kDartIsolateSnapshotInstructions");if (vm_isolate_snapshot_instructions_ == NULL) {  error_ = strerror(errno);  break;}vm_snapshot_data_ = dlsym(fd, "_kDartVmSnapshotData");if (vm_snapshot_data_ == NULL) {  error_ = strerror(errno);  break;}vm_isolate_snapshot_data_ = dlsym(fd, "_kDartIsolateSnapshotData");if (vm_isolate_snapshot_data_ == NULL) {  error_ = strerror(errno);  break;}
if (vm_snapshot_data_ == NULL || vm_snapshot_instructions_ == NULL || vm_isolate_snapshot_data_ == NULL || vm_isolate_snapshot_instructions_ == NULL) {  return false;}*vm_snapshot_data = reinterpret_cast <  const uint8_t * > (vm_snapshot_data_);*vm_snapshot_instructions = reinterpret_cast <  const uint8_t * > (vm_snapshot_instructions_);*vm_isolate_snapshot_data = reinterpret_cast <  const uint8_t * > (vm_isolate_snapshot_data_);*vm_isolate_snapshot_instructions = reinterpret_cast <  const uint8_t * > (vm_isolate_snapshot_instructions_);
FlutterProjectArgs args;// передаем все необходимое в argsargs.vm_snapshot_data = vm_snapshot_data;args.vm_snapshot_instructions = vm_snapshot_instructions;args.isolate_snapshot_data = vm_isolate_snapshot_data;args.isolate_snapshot_instructions = vm_isolate_snapshot_instructions;

Теперь, когда все есть, нужно собрать приложение особым образом, чтобы получить AOT-скомпилированный модуль под целевую платформу. Это можно сделать, выполнив команду из корня dart-проекта:

$HOST_ENGINE/dart-sdk/bin/dart \--disable-dart-dev \$HOST_ENGINE/gen/frontend_server.dart.snapshot \--sdk-root $DEVICE_ENGINE}/flutter_patched_sdk/ \--target=flutter \-Ddart.developer.causal_async_stacks=false \-Ddart.vm.profile=release \-Ddart.vm.product=release \--bytecode-options=source-positions \--aot \--tfa \--packages .packages \--output-dill build/tmp/app.dill \--depfile build/kernel_snapshot.d \package:lib/main.dart$DEVICE_ENGINE/gen_snapshot                               \    --deterministic                                             \    --snapshot_kind=app-aot-elf                                 \    --elf=build/lib/libapp.so                                   \    --no-causal-async-stacks                                    \    --lazy-async-stacks                                         \    build/tmp/app.dill

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

-Ddart.vm.profile=release \
-Ddart.vm.product=release \

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

output-dill нужен для построения нативной библитеки libapp.so.

Самыми важными для нас являются пути $DEVICE_ENGINE и $HOST_ENGINE два собранных движка под целевую (ARM) и хост-системы (x86_64) соответственно. Тут важно ничего не перепутать и убедиться, что libapp.so получается именно 32-битной ARM-версией:

$ file libapp.so libapp.so: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked

Запускаем и-и-и-и... вуаля! все работает!

И работает шустрее значительно! Теперь уже можно говорить о сравнимой производительности и эффективности рендеринга с исходным приложением на базе набора библиотек EFL. Рендеринг работает почти без запинки и почти идеально на простых приложениях.

3. Подключение устройств ввода

В рамках этой статьи мы пропустим то, как подружили Wayland и эмбеддер с пультом, мышкой и другими устройствами ввода. На их реализацию можно посмотреть в исходниках эмбеддера.

4. Интерфейс на ТВ-приставке под Linux и Android и как увеличить производительность в 23 раза

Коснемся еще нескольких нюансов производительности, с которыми столкнулись в продуктовом UI-приложении. Нас очень обрадовала идентичность работы UI как на целевом устройстве, так и на Linux и Android. Уже сейчас Flutter может вполне может похвастаться очень гибкой портируемостью.

Еще отметим интересный опыт оптимизации самого dart-приложения под целевую платформу. Нас разочаровала довольно низкая производительность продуктового приложения (в отличии от демок). Мы взяли в руки профайлер и начали копать идовольно быстро обнаружили активное использование функций __brcm_cpu_dcache_flush и khrn_copy_8888_to_tf32 во время анимаций (на платформе используется чип процессора Broadcom/BCM ). Явно происходило какое-то очень жесткое пиксельное программное трансформирование или копирование во время анимаций. В итоге виновник был найден: в одной из панелей был задействован эффект размытия:

//...filter: new ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),//...

Комментирование одного этого эффекта дало увеличение производительности приложения в дватри раза и вывело приложение на стабильные 50-60 fps. Это хороший пример того, как один специфический эффект может уничтожить производительность во всем приложении на Flutter при том, что он был зайствован всего-лишь в одной панели, которая большую часть времени была скрыта.

Итого

В результате мы получили не просто работающее продуктовое приложение, а работающее приложение с качественным фреймрейтом на Flutter на целевом устройстве. Форк и наша версия эмбеддера под RDK и другие платформы на основе Wayland находится тут: github.com/DEgITx/flutter_wayland

Надеемся, опыт нашей команды в разработке и портировании ПО для ТВ-приставок и Smart TV пригодится вам в своих проектах и послужит отправной точкой для портирования Flutter на других устройствах.

[!?] Вопросы и комментарии приветствуются. На них будет отвечать автор статьи Алексей Касьянчук, наш инженер-программист

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru