Pull to refresh

Set-Top-Box и опыты с Андроид в контейнере LXC

Reading time 6 min
Views 5.8K
Как возникла странная необходимость запустить Android в Linux контейнере, и что из этого получилось

Предыстория


Запустить Android в контейнере LXC на мой взгляд, вполне логичное решение, если хочется иметь прозрачность и надёжность Bare Linux и использовать огромный потенциал хороших (и не очень хороших) сторонних приложений для Android. Так же данная конфигурация представляет интерес, как платформа для отладки собственного образа AOSP в условиях, максимально близких к боевым.
Для экспериментов была выбрана прогрессивная и недорогая китайская приставка на базе 64-ти битного ARMv8 от Amlogic S905x (CPU — 4 ядра, ОЗУ — 2ГБ, MMC — 8ГБ). Так же веским аргументом явилась неплохая (по сравнению с другими вендорами) кодовая база в OpenSource и наличие исходника ядерного драйвера для Mali-450. А user-space библиотеки Mali сегодня в открытом доступе на официальном сайте ARM Limited. Библиотеки доступы в бинарном виде для Linux-FB, Linux-Wayland и Android.

Главной целью экспериментов были приложения онлайн-кинотеатров и приложения для работы с сетевыми медиа-хостингами. Например с Youtube в Linux сразу начались неприятности. Во-первых: хакерский метод получения ссылок на контент, путём разбора JS-скрипта и генерации сигнатуры (ранее реализованный в minitube от Тордини и в youtube-dl) начал регулярно ломаться, вследствие беспощадной борьбы Google с методами обхода рекламы. Во-вторых: максимальное разрешение контента было 720p — больше Google-API не выдавал. В-третьих: WebKit лишился нормального сопровождения и последнее время поддерживается только небольшой группой энтузиастов. Эта же участь постигла и его Qt-порт. В итоге, в один прекрасный момент, страница youtube/tv отказалась работать, сославшись на старость web-движка. Ну и под конец приподнёс сюрприз WebEngine (Qt-Chromium). Оказывается эта прелесть не поддерживает аппаратное ускорение. Исключение сделано только для его Android-порта, и маргинальной ветки VAAPI в Linux. Тупик. В общем, я не нашёл простого способа задействовать аппаратное ускорение декодирования видео для Chromium в Linux. Реализация VAAPI для Amlogic мне показалась тяжёлой и бесполезной работой. Пощупал я и pepper plugin — к сожалению PPAPI не позволяет проигрывать offscreen видео.

Android


Почему бы не запустить Android в контейнере? На подвиг вдохновил проект Anbox. Тщательное изучение Anbox показало, что он нам не подходит. Но идея была понятной. В статьях других авторов утверждалось, что запуск Android в контейнере — задача плёвая. Но на поверку всё оказалось гораздо сложнее. Простой настройкой файлов конфигурации мы отделаться не смогли.

Итак, собираю LXC и устанавливаю его в систему. Тест конфигурации ядра выявляет проблемы: надо включить поддержку пространства имён. Так как платформа встроенная, то всякие ненужности были отключены. Пришлось выявлять эти нужные ненужности.

Первым тестом была проверка работы Busybox в контейнере. Убедившись, что все работает, я начал эксперименты.

Первоначальный вид /var/lib/lxc/abox.conf:

lxc.rootfs = /var/lib/lxc/abox/rootfs 
	lxc.rootfs.backend = dir 
	lxc.utsname = abox 
	lxc.pts = 1024 
	lxc.cap.drop = mac_admin mac_override 

Качаем испохабленный китайскими ручонками AOSP 6.0.19. От ванильной версии его отличает наличие нормального лаунчера, заточенного под дисташку и жёскто пропатченного surfacelinger с поддержкой некоторых особенностей аппаратной платформы Amlogic. Ванильный AOSP был впоследствии также протестирован.

Небольшое отступление от темы: Китайцы, адаптируя софт, плюют на все правила, установленные сообществом. Вот например ядро 3.14.29. Этот ничего не говорящий номер релиза ядра используется почти на всех железках на процессорах Amlogic S8xx и S9xx. Но, почти всегда они очень серьёзно отличаются друг от друга, вплоть до полной несовместимости старых модулей с новыми образами и наоборот. Похоже ядро правилось по принципу: «максимально быстрый выход изделия на рынок». Код не просто грязный — он отвратительного качества. Изменение конфигурации как правило приводит к ошибкам при компиляции или линковке образа или модулей. Патченый Android такого же качества, и принципы адаптации похожие. Почти все рекомендации команды AOSP проигнорированы.

Ну деваться то некуда! Собираем.

Попытка №1 Устанавливаем образ в контейнер, запускаем. Не работает. Анализ показывает, что отсутствуют объекты ядра: binder и ashmem. Дособираем модули ядра.

Попытка №2 Запускаем снова. Падает installd. Выясняется, что оригинальный binder знать не знает про namespaces. Тянем биндер из Anbox.

Попытка №3 Запускается и сразу уходит на перезагрузку. Оказывается init хочет SELinux и без него работать отказывается.

Попытка №4 Включаем SELinux. Получаем вязанку проблем для хост-системы. Пришлось отключить, во-всяком случае пока — до выяснения сути и теории процесса. SELinux можно отключить и в командной строке при загрузке ядра, но я так и не понял, как передавать параметры в контейнер. Пришлось влезть в исходник init и грубо поправить его поведение. Это было первое и последнее хирургическое вмешательство, которое аукнулось мне позже.

Попытка №5 Процесс загрузки дошёл до зиготы. В логах ругань из ядра на UID init. В биндере (и биндере из Anbox) UID процесса-владельца жёскто сравнивается с единицей. Единственный способ — отключить проверку, тем более, что в контейнере эта проверка лишена смысла.

Попытка №6 Всплыли конфликты, связанные с совместным доступом к управлению оборудованием. Комментирую в скриптах init управление USB и Bluetooth. Убираю все записи из fstab, и запрещаю в скриптах монтирование и проверку всех носителей. Теперь добавим карту монтирования в конфигурацию контейнера. Она содержит только одну строку. Каталог /mnt/lxc.data смонтирован на хосте в реальный раздел MMC.

lxc.mount.entry = /mnt/lxc.data data auto rw,bind 0 0

Попытка №7 На экране появились прыгающие шарики, загрузка длится долго, потому как образ Android смонтирован по NFS, а так же идёт генерация dexx в каталоге /data. Повторная загрузка выполняется в разы быстрее. И вот, наконец, появился лаунчер.
Будем считать это последней попыткой, потому как, в общем всё работает и надо допилить детали.

Сеть не работает, точнее работает, но некоторые приложения оценивают её работоспособность по состоянию сетевых интерефейсов. Кривые руки, одним словом. Для устранения этого недостатка на хосте поднимаем сетевой мост (bridge) и виртуальный интерфейс (veth).

lxc.network.type         = veth 
	lxc.network.flags        = up 
	lxc.network.name         = eth1 
	lxc.network.link         = br0 
	lxc.network.veth.pair    = veth-01 
	lxc.network.ipv4         = 10.0.0.10/24 
	lxc.network.ipv4.gateway = 10.0.0.1 
	lxc.network.hwaddr       = 00:FE:CD:BA:09:87

Так же надо поднять сервер DHCP, в противном случае будут проблемы с DNS. К сожалению, Android не анализирует resolv.conf и его расположение в файловой системе не играет никакой роли. Можно настроить сетевое соединение вручную, но, при удалении данных с раздела data все настройки сбросятся.

Итоги


Работают все стоковые приложения. С установленными из маркета есть проблемы. Например: Youtube.tv версии 3 потребовал обновить Google service framework, после чего система сломалась. Всплыла проблема с keystore (пока не решена). Так же временно отключен TEE, соответственно widevine не работает. Игрушки и приложения без особых требований к оборудованию работают нормально. Chrome крутит HTML5 video с программным декодером, цеплять аппаратный декодер отказывается. По этому поводу существует мнение о криво запиленном китайцами AOSP. Но ванильный AOSP запускает лаунчер с тач-скрином — дисташкой управлять невозможно.

Послесловие


В ближайшей перспективе — сделать лаунчер запуска Android приложений напрямую из Linux. Пример этому имеется в исходнике wpa-supplicant. Так же можно подглядеть, как это сделано в Anbox.

Спасибо за внимание!

Дополнение 1


На днях проверил масштабируемость приложений Qt. Изначально, клиентское приложение IPTV написано на QML для Linux. Плеер работает через QtMultimedia plugin. В процессе компиляции всплыли проблемные зависимости. К счастью, ограничилось всё QtDbus, которого нет в Android. До сих пор не могу понять, зачем для Андроида нужно было изобретать велосипед — binder. Чем разработчиков DBus не устраивал? Тем что он в user-space работает? Или лицензионные соображения?
Отключил DBus. Это было безболезненно, так как канал был нужен для небольшого функционала, связанного с операционной системой. Собрался apk. Сложностью со сборкой нет, так как использую QtCreator (и вам рекомендую).
В AOSP пришлось нарисовать Mediaplayer bridge — наследник от android::MediaPlayerInterface. В нём были реализованы методы setDataSource() и stop(). Для остальных сделаны затычки. setDataSource имеет три интерфейса. Реализовать потребовалось только:
setDataSource(const sp<IMediaHTTPService> &httpService,
                       const char *uri, 
                       const KeyedVector<String8, String8> *headers)


Если захотите крутить файлы с медиа-носителей, придётся повозиться с
setDataSource(int fd, int64_t offset, int64_t length)

Имя файла нужно будет получить через "/proc/self/fd/" + fd;


После установки, приложение заработало и пошла трансляция. Супер! Я ожидал вороха проблем, а их почти нет. Спасибо разработчикам Qt и QtCreator за огромную и полезную работу!
В итоге получил такую связку: на хосте запущен демон плеера. В контейнере — клиентская программа и прокладка, транслирующая вызовы android::Mediaplayer на хост. Связка работает через UDP-socket.
Tags:
Hubs:
+7
Comments 11
Comments Comments 11

Articles