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

Тв-приставки

Как разработать аналог Zoom для ТВ-приставок на RDK и Linux. Разбираемся с фреймворком GStreamer

29.09.2020 16:20:30 | Автор: admin
Сценарии: как использовать приложение для видеоконференций на SmartTV и ТВ-приставкахСценарии: как использовать приложение для видеоконференций на SmartTV и ТВ-приставках

Пандемия COVID-19 стала катализатором для новых полезных сервисов. Например, Zoom стал настолько успешным, что по стоимости обогнал в этом месяце IBM. Нас вдохновил этот пример, и мы решили пойти еще дальше: а что если онлайн-конференции реализовать на приставках и Smart TV, чтобы общаться не только по работе, но устраивать удаленные посиделки на диване с друзьями? Но ведь тогда можно на футболе вместе покричать, и кино посмотреть или спортом заняться под контролем тренера.

Почему-то у операторов цифрового ТВ такой услуги не оказалось, хотя с инженерной точки зрения все эти функции вполне можно реализовать на ТВ-приставках на базе Linux/Android и RDK. Мы это проверили на практике и вот теперь делимся с читателями Хабра своим рецептом создания аналога Zoom и видеоконференций через Smart TV. Разберем архитектуру решения и кодирование видеопотока с использованием GStreamer. Информацию для работы с этим фреймворком мы собирали по крупицам, но оно того стоило.

Разбираемся с архитектурой

При разработке архитектуры приложений для ТВ-приставок важно учитывать потенциальные ограничения программной и аппаратной части этих устройств. Те, кто создает desktop-программы, как правило, не сталкиваются с этими сложностями, а для embedded-разработчиков это обычное дело.

Итак, с чем мы столкнемся на ТВ-приставках:

  1. Ограниченность ресурсов процессоров и самих приставок. Чаще всего в STB-устройствах используются разнообразные ARM-процессоры, они несут в себя ряд ограничений и дополнительных задач, в частности необходимость использования аппаратного кодирования/декодирование видеопотока. Всегда нужно учитывать, что быстродействие одно из узких мест.

  2. Разность архитектур в приставках разных производителей. Некоторые базируются на Android, другие на RDK, третьи на дистрибутивах на базе Linux со своими ограничениями и нюансами. Поэтому на старте разработки лучше выбрать наиболее общие и кроссплатформенные решения в разных модулях программы. Уже не говоря о поддержке desktop-версии. А уже потом переходить к частным случаям.

  3. Сетевые ограничения. Многие приставки работают как по Ethernet так и по wifi. Сжатие и передача видео/аудиопотока еще одно узкое место в приложениях такого рода.

  4. Безопасность передачи потока и др. вопросы безопасности данных.

  5. Поддержка камер и микрофонов на встроенных платформах.

Теперь можно разложить по полочкам и саму архитектуру. Аналог Zoom для ТВ-приставок будет состоять из нескольких больших компонентов и модулей:

  • Захват видеопотока

  • Захват аудиопотока

  • Сетевой модуль

  • Модуль кодирования видео/аудиопотока

  • Модуль декодирования видео/аудиопотока

  • Вывод видеоконференции на экран

  • Вывод звука

  • Цветовые преобразования

  • Несколько других второстепенных компонентов

В упрощенном виде один из вариантов архитектуры можно изобразить так:

Архитектура приложения для видеоконференций через Smart TVАрхитектура приложения для видеоконференций через Smart TV

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

Кодирование/декодирование потока

1) Преимущества GStreamer

Как мы уже отметили, одним из узких мест является передача видеопотока. Предположим у вас есть камера, отдающие кадры на скорости 30 кадров с секунду на небольшом разрешении 640x480. Итого, в RGB24 получается:

640 х 480 х 3 х 30 = 27 648 000 байт в секунду, т.е. более 26 Мбайт в секунду, что по понятным причинам совершенно неприемлемо в рамках сетевой передачи.

Один из вариантов решения реализовать кодирование какой-нибудь библиотекой. И тут, как можно уже было догадаться из названия статьи, выбор пал на фреймворк GStreamer. Почему именно эта библиотека? Вот несколько ее преимуществ на фоне остальных решений:

  1. Хорошая кроссплатформенность и отличная поддержка Linux и Android.

  2. На RDK Gstreamer является стандартом кодирования/декодирования и включен в дистрибутив по-умолчанию.

  3. Поддержках широкого набора модулей, фильтров и кодеков. Тот же FFmpeg, который можно использовать для тех же целей, де-факто является лишь одним из модулей GStreamerа.

  4. Простота построения конвейера (pipeline). Выстроить цепочку кодирования/декодирования не составляет большого труда, а сам подход конвейера позволяет легко заменять кодеки, фильтры и прочее без лишнего переписывания кода.

  5. API для работы С/C++ идет комплекте.

  6. Поддержка аппаратных кодеров/декодеров, в частности OpenMAX API что крайне важно при работе с ТВ-приставками.

2) Знакомимся с GStreamer разбираемся с конвейерами

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

gst-inspect-1.0 позволит посмотреть список доступных кодеков и модулей, так что можно сразу увидеть, что будет работать, и выбрать набор фильтров и кодеков.

gst-launch-1.0 позволяет запустить любой конвейер (pipeline).

В GStreamer используется схема декодирования, по которой поток последовательно проходит через разные компоненты, начиная с source, заканчивая sink-выводом. При этом source может быть любым файл, устройство, для вывода (sink) также можно указать нужное файл, экран, сетевые выводы и протоколы (наподобие RTP).

Подробнее про использование утилит и принцип построения конвейера можно почитать в другой статье на Хабре. Классический пример проигрывания mp4-файла:

gst-launch-1.0 filesrc location=file.mp4 ! qtdemux ! h264parse ! avdec_h264 ! videoconvert ! autovideosink

На входе принимается mp4-файл, который проходит через демуксер mp4 qtdemux, парсер h264, затем через декодер, конвертер и, наконец, идет вывод на экран.

Можно заменить autovideosink на filesink с параметром файла и вывести декодированный поток прямиком в файл.

3) Пишем приложение на GStreamer C/C++ API. Попробуем декодировать

Теперь, когда мы разобрались немного с использованием gst-launch-1.0, проделаем все тоже самое, только уже в рамках приложения. Принцип останется тем же: мы встраиваем декодирующий конвейер (pipeline), однако уже с использованием библиотеки GStreamer и glib-событий.

О простейшем примере построения filesrc в filesink с кодом хорошо рассказано в другой хабрастатье GStreamer: элементы и контейнеры. Мы же рассмотрим чуть более реалистичный и сложный живой пример H264-декодирования.

Инициализация GStreamer-приложения происходит один раз при помощи

gstinit (NULL, NULL);

Если вы хотите сразу видеть происходящее в деталях, можно выстроить уровень логирования перед инициализацией

gst_debug_set_active(TRUE);gst_debug_set_default_threshold(GST_LEVEL_LOG);

Учтите: сколько бы у вас не было конвейеров в приложении, инициализировать gstinit достаточно один раз.

Создадим новый event-loop, в котором будут обрабатываться события:

GMainLoop *loop;loop = g_main_loop_new (NULL, FALSE);

И теперь можно начать выстраивать наш конвейер:

Объявим необходимые элементы, в частности сам конвейер как тип GstElement:

GstElement *pipeline, *source, *demuxer, *parser, *decoder, *conv, *sink;pipeline = gst_pipeline_new ("video-decoder");source   = gst_element_factory_make ("filesrc",       "file-source");demuxer  = gst_element_factory_make ("qtdemux",      "h264-demuxer");parser   = gst_element_factory_make ("h264parse",      "h264-parser");decoder  = gst_element_factory_make ("avdec_h264",     "h264-decoder");conv     = gst_element_factory_make ("videoconvert",  "converter");sink     = gst_element_factory_make ("appsink", "video-output");

Каждый элемент конвейера создается через gst_element_factory_make, где первым параметром идет тип, а вторым его условное название для GStreamer, на которое он впоследствии будет опираться, например при выдаче ошибок.

Также не мешало бы проверить, что все компоненты найдены, в противном случае gst_element_factory_make, возвращает NULL.

if (!pipeline || !source || !demuxer || !parser || !decoder || !conv || !sink) {// не инициализирован один элемент - остановкаreturn;}

Установим тот самый параметр location через gob_ject_set:

gob_ject_set (G_OBJECT (source), "location", argv[1], NULL);

Остальные параметры в других элементах можно выставлять аналогичным образом.

Теперь необходим обработчик сообщений GStreamer, создадим соответствующий bus_call:

GstBus *bus;guint bus_watch_id;bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));bus_watch_id = gst_bus_add_watch (bus, bus_call, loop);gst_object_unref (bus);

gst_object_unref и другие подобные вызовы нужны для очищения выделенных объектов.

Затем объявим сам обработчик сообщений:

static gbooleanbus_call (GstBus     *bus,          GstMessage *msg,          gpointer    data){  GMainLoop *loop = (GMainLoop *) data;  switch (GST_MESSAGE_TYPE (msg)) {    case GST_MESSAGE_EOS:      LOGI ("End of stream\n");      g_main_loop_quit (loop);      break;    case GST_MESSAGE_ERROR: {      gchar  *debug;      GError *error;      gst_message_parse_error (msg, &error, &debug);      g_free (debug);      LOGE ("Error: %s\n", error->message);      g_error_free (error);      g_main_loop_quit (loop);      break;    }    default:      break;  }  return TRUE;}

А теперь самое важное: собираем и добавляем все созданные элементы в единый конвейер, тот самый что выстраивали через gst-launch. Очередность добавления, естественно, важна:

gst_bin_add_many (GST_BIN (pipeline), source, demuxer, parser, decoder, conv, sink, NULL);gst_element_link_many (source, demuxer, parser, decoder, conv, sink, NULL);

Стоит также заметить, что такой подход линковки элементов прекрасно работает для потоковых выходов, но в случае именно проигрывания (autovideosink) необходима дополнительная синхронизация и динамическая линковка демуксера и парсера:

  gst_element_link (source, demuxer);  gst_element_link_many (parser, decoder, conv, sink, NULL);  g_signal_connect (demuxer, "pad-added", G_CALLBACK (on_pad_added), parser);static voidon_pad_added (GstElement *element,              GstPad     *pad,              gpointer    data){  GstPad *sinkpad;  GstElement *decoder = (GstElement *) data;  /* We can now link this pad with the sink pad */  g_print ("Dynamic pad created, linking demuxer/decoder\n");  sinkpad = gst_element_get_static_pad (decoder, "sink");  gst_pad_link (pad, sinkpad);  gst_object_unref (sinkpad);}

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

Ну и, наконец, переводим состояние конвейера в проигрывание:

gst_element_set_state (pipeline, GST_STATE_PLAYING);

И запускаем event-loop:

g_main_loop_run (loop);

После окончания этой процедуры все нужно почистить:

gst_element_set_state (pipeline, GST_STATE_NULL);  gst_object_unref (GST_OBJECT (pipeline));  g_source_remove (bus_watch_id);  g_main_loop_unref (loop);

4) Выбираем энкодеры и декодеры. Фоллбэки

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

Поможет нам в этом функция gst_element_factory_find, которая проверяет, есть ли у нас кодек в factory элементов:

if(gst_element_factory_find("omxh264dec"))decoder  = gst_element_factory_make ("omxh264dec",     "h264-decoder");elsedecoder  = gst_element_factory_make ("avdec_h264",     "h264-decoder");

В данном примере мы приоритезировали выбор аппаратного декорера OMX на платформе RDK, и в случае его отсутствия будем выбирать программную реализацию.

Другой крайне полезной, но еще более редко используемой функцией является проверка того, что же мы на самом деле инициализировали в GstElement (какой кодек):

gst_plugin_feature_get_name(gst_element_get_factory(encoder))

Можно проделать это таким незамысловатым способом и вернуть название инициализированного кодека.

5) Цветовые модели видео

Не стоит оставлять без внимания и цветовые модели, раз уж мы заговорили о кодировании видео с камер. И тут чаще подразумевается YUV, нежели RGB.

Камеры просто обожают цветовую модель YUYV. Но это то, с чем любит работать GStreamer, для него более привычна модель I420. На выходе, если речь не идет о выводе в gl-фрейм, у нас также будут выходить I420-кадры. Будьте готовы подставить необходимые фильтры и выполнить преобразования. Некоторые энкодеры умеют работать и с другими цветовыми моделями, но чаще это исключения из правил.

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

Разбираемся с буферами и берем данные на лету

1) Буфер ввода

Пришло время разобраться с потоками данных. До этого момента мы просто кодировали то, что есть в файле, через filesrc и выводили все в тот же filesink или на экран.

Теперь будем работать с буферами и входами и выходами appsrc / appsink. Почему-то этому вопросу почти не уделиливнимания в официальной документации.

Как же организовать постоянный поток данных в созданных конвейерах, а точнее подать буфер на выход и получить кодированный или декодированный буфер выхода? Допустим мы получили картинку с камеры, и нам нужно ее закодировать. Мы уже определились с тем, что нам потребуется фрейм в I420. Допустим он у нас есть, что дальше? Как пропустить картинку через весь поток конвейера?

Вначале установим обработчик события need-data, который будет запускаться при необходимости подачи данных в конвейер и начнем подавать входной буфер:

g_signal_connect (source, "need-data", G_CALLBACK (encoder_cb_need_data), NULL);

Сам обработчик имеет следующий вид:

encoder_cb_need_data (GstElement *appsrc,          guint       unused_size,          gpointer    user_data){  GstBuffer *buffer;  GstFlowReturn ret;  GstMapInfo map;   int size;   uint8_t* image;  // get image  buffer = gst_buffer_new_allocate (NULL, size, NULL);  gst_buffer_map (buffer, &map, GST_MAP_WRITE);    memcpy((guchar *)map.data, image,  gst_buffer_get_size( buffer ) );  gst_buffer_unmap(buffer, &map);  g_signal_emit_by_name (appsrc, "push-buffer", buffer, &ret);  gst_buffer_unref(buffer);}

image это, условно говоря, псевдокод буфера картинки в I420.

Далее мы создаем через gst_buffer_new_allocate буфер необходимого размера, который будет соответствовать размеру буфера картинки.

При помощи gst_buffer_map устанавливаем буфер в режим записи и, используя memcpy, копируем нашу картинку в созданный буфер.

И, наконец, сигнализируем GStreamу о том, что буфер готов.

Ремарка: очень важно после записи использовать gst_buffer_unmap, а также очищать буфер после использования при помощи gst_buffer_unref. Иначе будет утечка памяти. В скудном количестве доступных примеров никто особо насчет вопроса освобождения памяти не заморачивался, а он, понятное дело, важный.

Теперь, когда мы закончили с обработчиком, нужно проделать еще одну вещь: настроить caps на поступление ожидаемого формата.

Делается это перед установкой обработчика сигнала need-data:

g_object_set (G_OBJECT (source),        "stream-type", 0,        "format", GST_FORMAT_TIME, NULL);g_object_set (G_OBJECT (source), "caps",gst_caps_new_simple ("video/x-raw","format", G_TYPE_STRING, "I420","width", G_TYPE_INT, 640,"height", G_TYPE_INT, 480,"framerate", GST_TYPE_FRACTION, 30, 1,NULL), NULL);

Как и все параметры GstElement, установка параметров осуществляется через g_object_set.

В данном случае мы задали тип потока, и его caps формат данных. Указав, что на выход appsrc будет поступать данные I420 c разрешением 640x480 и частотой 30 кадров в секунду.

Частота в нашем случае, да в целом, не играет роли. На практике мы не замечали, чтобы GStreamer как-то ограничивал вызовы need-data по частоте.

Готово, теперь наши кадры поступают в энкодер.

2) Буфер вывода

А теперь разберемся, как получить кодированный поток на выходе.

Подключаем обработчик к sink pad:

GstPad *pad = gst_element_get_static_pad (sink, "sink");  gst_pad_add_probe  (pad, GST_PAD_PROBE_TYPE_BUFFER, encoder_cb_have_data, NULL, NULL);  gst_object_unref (pad);

Похожим образом мы подключились к другому событию sink pad GST_PAD_PROBE_TYPE_BUFFER, которое будет вызываться по мере поступления буфера данных в sink pad.

static GstPadProbeReturnencoder_cb_have_data (GstPad * pad,                        GstPadProbeInfo * info,                        gpointer user_data) {  GstBuffer *buf = gst_pad_probe_info_get_buffer (info);  GstMemory *bufMem = gst_buffer_get_memory(buf, 0);  GstMapInfo bufInfo;  gst_memory_map(bufMem, &bufInfo, GST_MAP_READ);  // bufInfo.data, bufInfo.size  gst_memory_unmap(bufMem, &bufInfo);  return GST_PAD_PROBE_OK;}

Колбэк имеет похожую структуру. Теперь нужно достучаться до памяти буфера. Вначале получаем GstBuffer, затем указатель его памяти, используя gst_buffer_get_memory по индексу 0 (как правило задействован только он). И, наконец, используя gst_memory_map, получаем адрес буфера данных bufInfo.data и его размер bufInfo.size.

Фактически мы добились цели получили буфер с кодированными данными и его размер.

Итак, мы рассмотрели ключевые и самые интересные компоненты из приложения для Smart TV аналога Zoom для ТВ-приставок: архитектуру, модули кодирования / декодирования через GStreamer, буферы ввода / вывода и используемые цветовые преобразования.

Для операторов цифрового ТВ такая программная платформа может стать новым абонентским сервисом. Для нас инженеров новым интересным embedded-проектом для реализации на разных приставках на базе RDK, Linux и Android. А для всех остальных возможностью приятно проводить время за совместным просмотром фильмов и спортивных матчей, занятий спортом и посиделок с близкими в период карантина или удаленной работы.

Эту идею с сервисом видеоконференций по Smart TV можно развивать и дальше, как с точки зрения инженерных решений, так и по сценариям их использования. Так что делитесь мыслями в комментариях.

Подробнее..

Эволюция системы обновления Android

17.12.2020 06:12:58 | Автор: admin
Индиана устанавливает новый активный раздел: меняет золотого идола на мешок с песком в легендарной сцене из фильма Индиана Джонс: В поисках утраченного ковчегаИндиана устанавливает новый активный раздел: меняет золотого идола на мешок с песком в легендарной сцене из фильма Индиана Джонс: В поисках утраченного ковчега

В этой статье мы рассмотрим все возможные варианты обновления прошивки на устройствах под управлением Fuchsia Android. Особое внимание уделим самому популярному способу обновлению по воздуху или OTA (over-the-air) и расскажем об этапах его развития.

Итак, как можно обновить Android на мобильных устройствах? Занимаясь разработкой ТВ-приставок под управлением этой ОС, мы определили для себя 4 способа, отбросив совсем уж экзотические варианты:

  1. перепрошивка flash-памяти через аппаратный интерфейс JTAG (если есть);

  2. перепрошивка flash-памяти с использованием загрузчика (bootloader);

  3. обновление через Recovery Mode;

  4. OTA (over-the-air).

Рассмотрим подробнее каждый из вариантов.

1. Обновление Android через JTAG-интерфейс

Вариант с JTAG позволяет обновлять устройство только локально и требует подключения девайса с Android к хосту, например, по USB-интерфейсу. Так как перепрошивается flash-память, новую версию Android можно поставить на прошивку с другими ключами безопасности, да и в целом не сильно стесняться в выборе версий самой Android, версии собранной прошивки или переконфигурации разделов flash-памяти.

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

2. Обновление Android через Recovery Mode

Обычно загрузчик является проприетарным, его разрабатывает производитель чипа. Именно bootloader инициализирует доверенную среду выполнения (TEE, trusted execution environment) и проверяет целостность разделов boot и recovery перед переносом выполнения в ядро Linux. Сам загрузчик часто является составным, часть его уровней может быть открытой (например, на базе U-boot), а часть проприетарной.

Bootloader Android позволяет перепрошивать flash-память устройства подготовленными образами разделов. Для этого используется протокол fastboot либо его аналог (в случае Amlogic это будет протокол WorldCup Device). Fastboot,как и его аналог WorldCup Device, это протокол взаимодействия с bootloader через USB-интерфейс или локальную сеть Ethernet.

Для перепрошивки необходимо подключить устройство через USB к хосту (есть вариант использовать LAN Ethernet), перевести загрузчик (bootloader) в специальный update-режим и в этом режиме перепрошить flash-память устройства.

Плюсы и минусы данного метода всё те же, что и для JTAG: так как обновление проходит без участия самой системы Android, при перепрошивке нет ограничений, связанных с версией системы/сборки или ключами безопасности.

Но, как всегда, есть одно НО. :-) Bootloader должен быть разблокирован, а это значит, что мы можем перепрошить сам загрузчик или разделы устройства. Блокировка/разблокировка производится командой fastboot flashing lock/unlock, но для выполнения этой команды может понадобится пароль, установленный тем, кто добрался до этого устройства раньше вас (обычно это производитель).

3. Обновление Android через Recovery Mode и OTA

Если первые два варианта обновления оставались неизменными на протяжении всего времени развития Android, то следующие два варианта обновление через Recovery Mode и OTA реализуются средствами самой Android и эволюционировали вместе со всей ОС.

Стоит упомянуть, что Recovery Mode и OTA это два различных варианта вызова движка обновления Android.

Recovery или non-A/B System Updates

Recovery и движок обновления updater (bootable/recovery/updater) это как раз то, с чего началась система обновления Android (располагается в bootable/recovery в дереве исходников AOSP).

Схема обновления Recovery (или non-A/B System Updates) задействует специальный раздел восстановления (Recovery), где содержится специальная ОС на основе ядра Linux. Эта ОС на базе Linux содержит программное обеспечение для распаковки загруженного образа обновления и его применения к другим разделам. Так и проходит обновление Android.

Пример разметки flash-памяти на устройстве с Android 6.0:

Карта разделов Android 6.0.1

[mmcblk0p01] bootloader offset 0x000000000000, size 0x000000400000

[mmcblk0p02] reserved offset 0x000002400000, size 0x000004000000

[mmcblk0p03] cache offset 0x000006c00000, size 0x000020000000

[mmcblk0p04] env offset 0x000027400000, size 0x000000800000

[mmcblk0p05] logo offset 0x000028400000, size 0x000002000000

[mmcblk0p06] recovery offset 0x00002ac00000, size 0x000002000000

[mmcblk0p07] rsv offset 0x00002d400000, size 0x000000800000

[mmcblk0p08] tee offset 0x00002e400000, size 0x000000800000

[mmcblk0p09] crypt offset 0x00002f400000, size 0x000002000000

[mmcblk0p10] misc offset 0x000031c00000, size 0x000002000000

[mmcblk0p11] instaboot offset 0x000034400000, size 0x000020000000

[mmcblk0p12] boot offset 0x000054c00000, size 0x000002000000

[mmcblk0p13] system offset 0x000057400000, size 0x000060000000

[mmcblk0p14] data offset 0x0000b7c00000, size 0x0002ec200000

Сам процесс обновления происходит в два этапа:

  1. после загрузки с раздела Recovery происходит обновления всех остальных разделов Android;

  2. и уже после перезагрузки и запуска новой версии Android происходит обновление раздела Recovery.

При обновлении с использованием движка updater на первом этапе проверяется версия и цифровая подпись образа, поэтому откатить на старую версию ОС уже не получится.

Обновиться по схеме Recovery можно как локально, выбрав в bootloader режим Recovery Mode и запустив движок обновления updater через меню Recovery Mode, либо удаленно, через OTA, когда приложение, работающее в Android, вызывает тот же updater из Java. И как раз при таком удаленном запуске можно организовать массовое обновление целой серии устройств. Этот вариант используют операторы цифрового ТВ при обновлении своих абонентских ТВ-приставок.

Сам раздел Recovery при non-A/B-схеме обновления является физическим разделом во flash-памяти. С появлением A/B-схемы раздел Recovery переместился на RAM-диск в оперативной памяти устройства, но возможность сделать его отдельным физическим разделом так же осталась.

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

Одним из важных недостатков схемы Recovery или non-A/B System Updates является то, что при любом сбое во время обновления или битой прошивке мы получаем пусть и не кирпич (с раздела Recovery всё еще можно запустить устройство в Recovery Mode), но всё же не полнофункциональное и требующее восстановления устройство.

С этим, видимо, решено было что-то делать, потому что следующим этапом эволюции системы обновления стало бесшовное обновление (seamless updates) или A/B-схема обновления.

Бесшовное обновление или A/B-схема

Эта возможность появилась в Android 7.0, она реализована в новом движке update_engine, который располагается в system/update_engine в дереве исходников AOSP.

Главной особенностью A/B-схемы стало то, что в случае сбоев при обновлении можно загрузится с предыдущей рабочей версии системы Android. Flash-память устройства содержит дублирующиеся системные разделы или слоты (slot A и B), отсюда и название A/B system updates (вечная проблема с выбором названий). За выбор слота для загрузки (A или B) отвечает bootloader, анализируя состояние слотов.

Принцип бесшовного обновления Android по A/B-схеме (активный раздел отмечен птичкой)Принцип бесшовного обновления Android по A/B-схеме (активный раздел отмечен птичкой)

Итак, как же происходит обновление:

1) Загружая систему, например, со слотов A, мы скачиваем и прошиваем обновления на слоты B.

2) После перезагрузки со слотов B мы проверяем работоспособность системы, и, если все ОК, сообщаем bootloader, что обновление прошло успешно.

В случае проблем с обновлением bootloader вернется на старую версию прошивки после нескольких неудачных попыток загрузиться с новой системы.

На официальном сайте для разработчиков Android Source этот процесс расписан более детально в 9 шагах, также там объясняется, как все работает после перезагрузки.

Особенность бесшовной A/B-схемы обновление это съедение большего объема flash- памяти. Насколько большего? Это можно оценить по приведенным ниже схемам разделов для Android 9.0. Как уже упоминалось ранее, разработчик может выбирать, какую из схем A/B или non-A/B применять в конфигурации системы.

Карта разделов Android P (recovery)

[mmcblk0p01] bootloader offset 0x000000000000, size 0x000000400000

[mmcblk0p02] reserved offset 0x000002400000, size 0x000004000000

[mmcblk0p03] cache offset 0x000006c00000, size 0x000046000000

[mmcblk0p04] env offset 0x00004d400000, size 0x000000800000

[mmcblk0p05] logo offset 0x00004e400000, size 0x000000800000

[mmcblk0p06] recovery offset 0x00004f400000, size 0x000001800000

[mmcblk0p07] misc offset 0x000051400000, size 0x000000800000

[mmcblk0p08] dtbo offset 0x000052400000, size 0x000000800000

[mmcblk0p09] cri_data offset 0x000053400000, size 0x000000800000

[mmcblk0p10] param offset 0x000054400000, size 0x000001000000

[mmcblk0p11] boot offset 0x000055c00000, size 0x000001000000

[mmcblk0p12] rsv offset 0x000057400000, size 0x000001000000

[mmcblk0p13] metadata offset 0x000058c00000, size 0x000001000000

[mmcblk0p14] vbmeta offset 0x00005a400000, size 0x000000200000

[mmcblk0p15] tee offset 0x00005ae00000, size 0x000002000000

[mmcblk0p16] vendor offset 0x00005d600000, size 0x000040000000

[mmcblk0p17] odm offset 0x00009de00000, size 0x000008000000

[mmcblk0p18] system offset 0x0000a6600000, size 0x000050000000

[mmcblk0p19] product offset 0x0000f6e00000, size 0x00000800000

Карта разделов Android P (A/B-схема)

[mmcblk0p01] bootloader offset 0x000000000000, size 0x000000400000

[mmcblk0p02] reserved offset 0x000002400000, size 0x000004000000

[mmcblk0p03] cache offset 0x000006c00000, size 0x000000000000

[mmcblk0p04] env offset 0x000007400000, size 0x000000800000

[mmcblk0p05] logo offset 0x000008400000, size 0x000000800000

[mmcblk0p06] boot_a offset 0x000009400000, size 0x000001000000

[mmcblk0p07] misc offset 0x00000ac00000, size 0x000000800000

[mmcblk0p08] dtbo_a offset 0x00000bc00000, size 0x000000800000

[mmcblk0p09] dtbo_b offset 0x00000cc00000, size 0x000000800000

[mmcblk0p10] cri_data offset 0x00000dc00000, size 0x000000800000

[mmcblk0p11] param offset 0x00000ec00000, size 0x000001000000

[mmcblk0p12] boot_b offset 0x000010400000, size 0x000001000000

[mmcblk0p13] rsv offset 0x000011c00000, size 0x000001000000

[mmcblk0p14] metadata_a offset 0x000013400000, size 0x000001000000

[mmcblk0p15] metadata_b offset 0x000014c00000, size 0x000001000000

[mmcblk0p16] vbmeta_a offset 0x000016400000, size 0x000000200000

[mmcblk0p17] vbmeta_b offset 0x000016e00000, size 0x000000200000

[mmcblk0p18] tee offset 0x000017800000, size 0x000002000000

[mmcblk0p19] vendor_a offset 0x00001a000000, size 0x000040000000

[mmcblk0p20] vendor_b offset 0x00005a800000, size 0x000040000000

[mmcblk0p21] odm_a offset 0x00009b000000, size 0x000008000000

[mmcblk0p22] odm_b offset 0x0000a3800000, size 0x000008000000

[mmcblk0p23] system_a offset 0x0000ac000000, size 0x000050000000

[mmcblk0p24] system_b offset 0x0000fc800000, size 0x000050000000

[mmcblk0p25] product_a offset 0x00014d000000, size 0x000008000000

[mmcblk0p26] product_b offset 0x000155800000, size 0x000008000000

[mmcblk0p27] data offset 0x00015e000000, size 0x000245e00000

Если сравнить эти две конфигурации, то можно заметить, что раздел data при A/B-схеме меньше на 1,6 ГБ, и это цена дублирующихся системных разделов. Много это или мало каждый решает сам, ориентируясь на характеристики своего устройства/проекта.

Проект Treble

Следующие изменения в системе обновления произошли в Android 8.0. Начиная с Android O (8.0) и продолжая в Android P (9.0), Google реализует свой проект Treble. Идея проекта состоит в том, чтобы упростить технологический процесс создания обновления для андроид-устройства. Google предложил разделить с помощью неизменных интерфейсов части прошивки, созданием которых занимаются разные компании. Процесс разработки прошивки для конкретного девайса можно упрощенно разделить на следующие шаги:

  1. команда Android создает новую версию своей OC;

  2. разработчик чипа или системы на кристалле (Silicon Manufacturer) создает аппаратно-зависимые патчи для запуска этой версии Android на своих платах;

  3. и уже разработчики конечного устройства (Vendors) делают свою часть для реализации всех функций конкретного продукта для рынка электроники.

Проект Treble разделяет ОС Android с дополнениями от производителей чипов/СнК и код разработчика конечного устройства, так что теперь операционная система может получать обновления без реализации изменений от производителя устройства.

Разделение происходит как с помощью программного интерфейса (переход с Hardware Abstraction Layer 1.0 на HAL2.0), так и за счет выделения отдельных разделов на flash-памяти для Silicon Manufacturer и Vendor (выше в карте разделов Android 9.0 можно увидеть разделы odm, vendor, product).

Переход с HAL1.0 на HAL2.0 заключается в отказе от прямого связывания с системными библиотеками. Вместо этого, используя IPC Binder, можно подключаться к системным сервисам.

И еще одно небольшое, но полезное изменение: начиная с Android 8.0, в update_engine добавлена поддержка потоковых обновлений по A/B-схеме, в ходе которых идет прямая запись в слот B без необходимости промежуточного хранения данных в /data. Для таких потоковых обновлений практически не требуется временное хранилище, достаточно всего лишь 100 килобайт для сохранения метаданных.

При этом необходимо, чтобы http-сервер, используемый для скачивания обновления, поддерживал HTTP range requests или другими словами докачку.

Проект Mainline

Следующим серьезным этапом в развитии системы обновления Android стал проект Mainline. Реализация этого проекта началась с Android 10.0 и продолжилась в текущем Android 11.0.

Проект Mainline позволяет обновлять отдельные системные компоненты без обновления ОС целиком. Нужные данные загружаются через Google Play отдельно от OTA-обновления прошивки от производителя. Предполагается, что прямая доставка обновлений, не привязанных к оборудованию частей Android, позволит существенно сократить время получения обновлений, увеличить оперативность исправления уязвимостей и снизить зависимость от производителей устройств в поддержке безопасности ОС.

Для реализации проекта Mainline выбранные компонентов системы Android преобразуется в модули. Часть этих модулей имеет старый формат APK, а часть конвертируется в новый APEX-формат, который отличается от APK возможностью применения на раннем этапе загрузки системы. На случай возможных сбоев предусмотрен режим отката изменений.

С APEX-пакетами работает системный сервис APEX manager (apexd). Это нативный сервис, который после проверки распаковывает APEX-пакет в пользовательское пространство на диске и добавляет запись о нем в свою базу данных. При следующей загрузке системы APEX manager проверяет все пакеты из базы данных, создает loop-устройство для ext4-образа каждого APEX-пакета и монтирует его по пути /apex/name@ver.

Модули с обновлениями изначально будут поставляться с открытым кодом, они будут сразу доступны в репозиториях AOSP (Android Open Source Project) и смогут включать улучшения и исправления, подготовленные сторонними участниками.

В рамках проекта Mainline в Android 10 было добавлено 13 обновляемых модулей, а в Android 11 в дополнение к уже существующим прибавилось еще 11 модулей.

Схема Virtual A/B

Также в Android 11 к схемам non-A/B и A/B была добавлена схема Virtual A/B. Этот новый механизм обновления сочетает преимущества обоих предшественников, он обеспечивает устойчивое к сбоям обновление устройства, задействуя при этом минимальный объем flash-памяти. Это стало возможным благодаря созданию снимков файловой системы (snapshot) с использованием технологии Device-mapper (подсистема ядра Linux, позволяющая создавать виртуальные блочные устройства) и Dynamic Partitions.

Dynamic Partitions это система организации динамических разделов для Android. С ее помощью можно создавать, изменять размер или уничтожать разделы прямо в процессе обновления по воздуху (OTA). При использовании динамических разделов разработчикам больше не нужно беспокоиться о размере отдельных разделов, таких как system, vendor и product. Вместо них на устройстве выделяется суперраздел, внутри которого можно динамически изменять размер подразделов. Больше нет необходимости оставлять свободное пространство для будущих OTA-обновлений внутри отдельных образов разделов. Оставшееся свободное место в суперразделе теперь доступно для всех динамических подразделов.

И в заключении последние слухи конца 2020 года вишенка на торте. Google конвертирует Android Runtime в модуль Mainline. Android Runtime или ART это среда выполнения Android-приложений, включающая компиляцию байт-кода приложения в машинные инструкции. Так что есть вероятность, что уже в Android 12 можно будет обновить ART через GooglePlay, установив APEX-пакет.

Также, вероятно, система обновления Android мигрирует в Fuchsia, новую ОС Google, которая сейчас находится в процессе разработки. Они традиционно копируют удачные решения в своих программных продуктах. Так, например, update_engine для A/B-схемы, который применяется сейчас в Android, используется в еще одной ОC Google Chrome OS. Или еще один пример: в Fuchsia предлагается библиотека Machina, которая позволяет запускать Linux-программы в специальной изолированной виртуальной машине по аналогии с тем, как организован запуск Linux-приложений в той же Chrome OS.

Желаем всем успешных обновлений!

P.S. Как там было в Индиане Джонсе?
Как вы меня узнали?
У вас глаза вашего отца.
И уши моей матери. Но все остальное принадлежит вам.

Подробнее..

Категории

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

  • Имя: Макс
    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