Как верстальщики Apple кодируют видео с помощью JPEG, JSON и canvas
17 сентября 2012 :: 12 комментариев :: 12120 просмотров :: 1043 слова

Кто уже потянулся закрывать вкладку, испугавшись слова Apple, сообщаю: тут не будет новых айфонов, тут будет описание идеи верстки анимаций с использованием побайтового чтения изображений из JPEG-потока прямо на JavaScript.

Обещал ничего не писать про Apple и нарушаю. А всё потому что в руки мне попалась чудесная статья о том, как, собственно, в загнивающем капиталистическом Apple верстают известные любому, кто хоть раз заходил на их сайт промо-страницы новых девайсов. Те, на которых иногда что-то крутится, переливается, перемещается, добавляя некоторую изюминку простому, пусть даже стильно оформленному, рекламному буклету, без излишеств. В комментариях на Hacker News правильно заметили, что подобную технологию так же использует Sublime Text прямо на главной странице (что мне всегда нравилось у них на сайте, мегаудобно). Кстати, как будет понятно в конце статьи, немного более продвинутую.

С каждым годом скорость JS в браузерах увеличивается и таких изюминок на страницах сайта становится все больше. Ярким представителем использования JS для оживления страничек была лупа для демонстрации четкости экрана Retina на iPhone 4 или iPad 3. Этим эффектом пользовались и раньше, но как обычно после Apple он стал особенно популярен.

А недавно своя страничка появилась и у iPhone 5, раздел «Design» которой просто напичкан автоматически включающимися анимациями при скроллинге страницы. Однако если посмотреть в веб-инспекторе, тега video или богомерзкого флеша вы там не найдете. Там только canvas. А если потом посмотреть вкладку Network, можно увидеть пару странных JPEG'ов.

Наверное верстальщикам Apple слишком много платят? Или у них слишком много времени, чтобы заниматься подобными извращениями. Ладно флеш, мы знаем отношение этих ребят к флешу. Да и страничка с таким объемом высококачественной флеш-анимации доставляла небольшое жжение в заднем проходе (мы все еще помним, что некоторые браузеры на некоторых платформах так и не могут плавно скроллить странички при наличии на них флеша). Applе активно участвовала в создании h.264, имеет хороший браузер на всех своих платформах, неужели нельзя было пойти по простому пути и использовать тег video?

Но тег video тут не подходит по двум причинам. Первая — «некоторые браузеры», уже упоминавшиеся выше (ну и еще Firefox), не поддерживают h.264, вторая же причина немного сложнее — тег video на большинстве мобильных платформ сделан так, чтобы нормально работать только в полноэкранном режиме. Это удобно для просмотра ютюба, но нарушает всю фишку подобных промо-страниц. Да и на десктопах, если мне не изменяет память, тег не запускается без клика по play (секкурность, все дела). В комментариях предложили анимированные gif, видимо считая, что 255 цветов хватит всем и каждому. Смешно. Анимированные png так и не прижились во всех webkit-браузерах. Тоже не катит. И что делать?

Верстальщики Apple пытались решить эту проблему «в лоб» — загружая JPEG на каждый кадр анимации. Этот подход применялся даже пол года назад на промо-странице Macbook Retina, для двухсекундной анимации открытия крышки ноутбука понадобилось 5 мегабайт JPEG-файлов (более того, каждый — отдельным HTTP-запросом). И ведь дизайнеры Apple не дураки и знают про методы оптимизации. Не подходит.

И вот, на странице iPhone 5 мы видим «более лучший» © подход. На всю анимацию разблокировки айфона и красивого вылета иконочек уходит всего 1 Мб трафика и 5 HTTP-запросов.

Всё «видео» закодировано в этих файлах:

http://www.apple.com/iphone/design/images/unlock/unlock_manifest.json
http://www.apple.com/iphone/design/images/unlock/unlock_keyframe.jpg
http://www.apple.com/iphone/design/images/unlock/unlock_001.jpg
http://www.apple.com/iphone/design/images/unlock/unlock_002.jpg
http://www.apple.com/iphone/design/images/unlock/unlock_endframe.jpg

Логика для чтения всего этого хозяйства содержится в файле ac_flow.js, который интересующиеся могут почитать сами, воспользовавшись кнопочкой «Pretty Print» в веб-инспекторе.

Извращенный JS-видео-кодек от разработчиков Apple работает следующим образом: каждый кадр он обновляет только те части картинки, которые изменились с предыдущего. unlock_001.jpg и unlock_002.jpg содержат каждую обновляемую часть, а unlock_manifest.json определяет какую из них в каком кадре нужно подгружать.

Просто посмотрим на этот json.

Известно, что JPEG использует блоки 8x8 для кодирования изображений, так что Apple так же использует такой размер (blockSize в json), чтобы избежать наложения JPEG-артефактов от соседних блоков.

Параметр imagesRequired сообщает сколько изображений требуется подгрузить. Каждое изображение (unlock_001.jpg и unlock_002.jpg) на самом деле является JPEG-stream'ом, то есть не настоящим JPEG'ом, а последовательностью блоков 8x8, без заголовков и.т.д.

Массив frames выглядит как base64, чем он и является, но, грубо говоря, в «байте» теперь 6 «бит». И вот как это декодируется:

Каждый кадр состоит из 5 байтов инструкций. В первых 3 байтах содержится положение, в котором должен быть отрисован кадр на canvas'е. В последних двух — сколько блоков 8х8 прочитать. Например начало первого кадра — AAxAC. Нам говорят прочитать 2 блока (AC) и поместить их на позицию 49 (AAx) на canvas'е.

Заметьте, что нету никакого способа переиспользовать блоки. Они читаются из потока единожды (первый кандидат на доработку). Однако это делает файл-манифест меньше, а его формат более понятным.

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

Вот таким способом Apple кодирует видео. Однако эта задумка используется не только для видео. С помощью этой технологии так же реализовано, например, интерактивное вращение наушников на той же странице чуть ниже. Вращающиеся наушники можно схватить мышкой и повращать самому, после чего они продолжат так же вращаться сами по себе. И реализовано это именно с помощью выше описанной техники с дополнительным JavaScript'ом.

Посмотрим на профайлер в хроме:

Видно, что на отрисовку на canvas уходит очень мало времени, бОльшая часть времени тратится на декодирование кадров. Это приводит нас к первому минусу данного формата, перемотка в нём — очень дорогая операция. Таки да, чтобы отобразить кадр, сначала нужно отобразить все кадры до него! Ведь в каждом кадре изменяется лишь часть изображения. Вот так, например, высчитываются эти diff'ы.

Чтобы хоть как-то спасти ситуацию верстальщики Apple сделали две версии видео: вперед и назад, однако это увеличило размер файлов в 2 раза, но не решило проблему с отбрасыванием кадров (skip-frames). Так что при ручном вращении наушников так и заметны жуткие тормоза в «некоторых браузерах». По правде сказать, вращение наушников плавно работает только в одном браузере — Safari, что и следовало ожидать.

Очевидно, что разработчики Apple не остановятся на этом и будут совершенствовать свой «кодек». И если посмотреть код внимательнее, можно заметить куда именно:

Кажется, в Apple работают над новой версией скрипта, которая будет кодировать данные в PNG (да-да, я тоже переваривал минуты 2), вместо JSON + base64-строк. PNG так же хорошо поддерживается canvas'ом, а кроме того является еще и lossless-форматом, что положительно скажется на качестве изображений. Будет интересно посмотреть на идеи хранения данных в PNG и скорость работы этого подхода. Кстати, как я отмечал в начале статьи, Sublime Text как раз хранит свои данные в PNG и делает анимацию чуть иначе. Кадры хранятся в конце файла (пруф) и отображаются так же частями. Реализация выглядит более приятной, но у них случай чуть проще.

У вас уже появилась идея для написания библиотеки? Знаете готовую? В комментарии, мне интересно посмотреть.

Комментарии ↓
_ :: 17 сентября 2012 в 11:37 из Novosibirsk, RU #
0

Какая жесть. Какая жесть. Они молодцы. Ты теперь тоже так умеешь?

themylogin :: 17 сентября 2012 в 11:50 из Novosibirsk, RU #
0

В PNG блобы таскать в браузер - охуительная идея!

vas3k :: 17 сентября 2012 в 11:56 из Novosibirsk, RU #
0

themylogin, ага, по степени извращенности чем-то напомнило мне <a href="https://github.com/videlalvaro/gifsockets">gifsockets</a> — реализацию сокетов через gif-картинки с поддержкой даже ie6.

Очень вовремя разработчики Sublime тоже решили опубликовать статью как они делают анимации: http://www.sublimetext.com/~jps/animated_gifs_the_hard_way.html И даже выложили либу на github: https://github.com/sublimehq/anim_encoder

themylogin :: 17 сентября 2012 в 20:16 из Novosibirsk, RU #
0

И ализар на хабр написал статью, правда, не столь детальную :(

Un1oR :: 18 сентября 2012 в 06:08 из Novosibirsk, RU #
0

Через полчаса, ога)

vas3k :: 18 сентября 2012 в 06:18 из Novosibirsk, RU #
0

themylogin, Un1oR, всё равно я был первый кекеке :P И на такой перевод ушла почти пара часов, а он как раз в самом сложном месте и остановился, где даже я минут 20 тупил. А он не программист, а хабрашлюшка, ему платят за количество постов, а не качество, так что для него нормально.

ReDetection :: 20 сентября 2012 в 15:10 из Novosibirsk, RU #
0

действительно, охуительная идея! и с wave-звуком так же можно, почему бы и нет. ну разве что наушники не обрадуются, если кто решит послушать такой звук

Комментирование доступно только участникам Клуба

Войти
Еще? Тогда вот