thedeemon: (Default)
Давайте я сразу покажу картинку, а потом немного расскажу о чем это:



Есть у меня свой видеокодек - ScreenPressor - изначально 100% беспотерьный кодек для записи и/или передачи экрана, но также умеющий опционально немного потерять качества (нижние биты цветов), если попросят. Для него в стародавние времена был сделан плеер на Flash'e. Года три назад я экспериментировал с идеей перевода его на JS, Dart или ASM.js, портировал на них кусочек, померял скорость, показал клиентам, те сказали "пока не надо, флэша хватает". А в этом году, когда Flash стал уже совсем не комильфо, и бразуеры его стали выключать, клиенты опомнились и говорят "хотим JS! И скорости!". А у меня как раз поспела новая 3-я версия кодека, где вместо арифметического кодера модный ANS, и сжатие параллелизовано получше, на ряде тестов кодирование+декодирование раза в 2 ускорилось.
Dart к этому времени уже давно отказался от своей ВМ в браузере. ASM.js по-прежнему очень жирный, и не везде он хорошую скорость показывал, да и WebAssembly его вытесняет. Но WebAssembly еще рановато использовать, когда у тебя корпоративные клиенты с не самыми новыми браузерами. Флэшовый плеер был написан на Haxe, который, как известно, умеет и JS генерить. Взял я тогда исходники флэшовой версии плеера и задействовал OpenFL - это библиотека на Haxe, которая дает API Flash'a и позволяет их использовать на других таргет платформах, в моем случае JS.
И шо я вам скажу за OpenFL. В принципе, она работает. Берешь код, что был заточен на Flash, раз-два, и вот уже готов один толстый .js файл и рядом парочка маленьких (две библиотечки-зависимости, одна из которых - Howler.js, звукоигралка). И он даже работает. Но плохо и медленно. И тут начинается долгая работа по адаптации, исправлению косяков и обходу граблей.
Во Flash'e обычные массивы были медленные, но был отдельный волшебный API для работы с быстрым типизированным массивом чисел (вроде typed array и ArrayBuffer в JS), но только одним за раз. Соответственно, в моей флэшовой реализации кодека все его данные жили в этом одном массиве. Его реализация в OpenFL мягко говоря не очень шустрая, потому надо все переписать на использование всяких родных Uint8Array.
Потом обнаружилась подстава с загрузкой данных. Казалось бы, элементарная вещь - запросить по HTTP какой-то файл, и по мере поступления бинарных данных как-то их разбирать и обрабатывать. Во Flash'e это отлично делалось стандартным Loader'ом с колбэком. OpenFL его реализует через XMLHttpRequest (а что ж еще?), но в нем обычно колбэки о получении данных срабатывают лишь когда все данные уже получены, а не в процессе. Точнее, есть заднепроходный способ сделать так, чтобы он колбэки дергал в процессе, но OpenFL его не использует, пришлось переписать работу с XHR самому.
Или, например, в какой-то момент оказалось, что больше половины времени уходит на то, чтобы декодированный кадр оказался в нужном битмапе (BitmapData.setPixels()). Сколько есть способов разместить значения R,G,B в RGB32? И это сказывается. Кодек подразумевает один порядок байтов в RGB32, Flash - другой, битмап DOMа браузера - третий. Получилось, что там данные дважды конвертировались, причем делали это весьма неоптимальным способом: в исходниках OpenFL для работы со всякими RGB32 и BGR32 есть красивые классы и проперти, но как посмотришь, что за JS код получается в итоге, сразу волосы шевелятся, столько лишних действий ради перестановки пары байт. Убрал все эти лишние конвертации, сразу полегчало, а то они около 60 мс на кадр съедали.
Ну и используемый для звука howler.js не мог не подложить свинью. Звук в видеофайле же раскидан кусочками, надо уметь эти кусочки в памяти собрать, декодировать, потом когда надо проиграть. А howler сделан как обычно для упрощения и унификации, в итоге он только целиком аудиофайл может загружать, а так чтобы из памяти - хрен. Пришлось напрямую WebAudio API использовать, благо это просто, все заработало, но только не в Internet Explorer'e.
В общем, [insert expletive] на дворе конец 2017 года, Flash, в котором все просто работало, причем одинаково во всех браузерах, выкинули, а HTML5, который давно считается годной заменой, на деле на 90% состоит из плохо работающих костылей и подпорок.
С размером тоже смешно вышло - весь плеер на Flash'e весил около 30 КБ. Вариант на JS же весит больше мегабайта, ибо включает много ненужных частей из OpenFL, которые Closure Compiler не смог выкинуть.
Отдельное удовольствие - отладка. Вот есть у нас массив Uint8Array на 100 элементов. Что будет, если мы попробуем записать в 103-й? Ничего. Вообще ничего. Ни исключения, ни изменения его длины, тишина. А что будет, если прочитать 103-й элемент? Просто undefined, опять никаких сообщений о вылете за пределы. Удачи в поиске ошибок!
Тем не менее, в итоге все получилось. Новый pure JS player работает, и видимо даже быстрее флэшового. Примеры: маленькое видео со звуком, побольше и без звука.
На картинке в начале поста результаты замеров, когда сначала делался переход на 0 кадр, а затем на 490-й (при том что следующий ключевой кадр - 500-й). После перехода на нулевой кадр плеер автоматически декодирует несколько следующих, заполняя свой буфер, поэтому переход потом на 490-й кадр означает в данном случае декодирование 480 кадров. Эту операцию и мерял, на видео 1364х768 (1 миллион точек, 4 МБ данных RGB32 на один кадр). Там в среднем в одном кадре не так много изменений, и большая часть кадра получается копированием из предыдущего (в том числе при скроллах и движении окошек), но все равно, способность JavaScript реализации ворочать такие 4 МБ мешки по 150-200 штук в секунду (на ноуте с Core i5) впечатляет. Нативный же код, где компилятор умеет в векторизацию, в 2-4 раза быстрее. На других видео цифры могут быть другими, тут многое зависит от декодируемого контента, его сложности.
А, еще что занятно: 3-я нативная версия кодека заметно быстрее 2-й. А вот их JS реализации оказались наоборот, третья несколько медленнее. Что хорошо для нативного кода в плане оптимизаций, не всегда хорошо для интерпретируемого.
thedeemon: (office)
Ребе [livejournal.com profile] metaclass уже не первый раз поднимает эту тему: можно ли иметь строгую типизацию записей, но не объявляя их заранее? Вот пара примеров из используемых мною языков.

Первый пример: Haxe.
function makeData()
{
  return { a: 99, b : " bottles of beer" };
}
	
function useData(data):Void
{
  var x:Int = data.a;
  var y:String = data.b;
  trace(x, y);
}	

...
var t = makeData();
useData(t);

Язык со статической типизацией. Где типы не указаны, компилятор сам их выводит. Тут функция makeData производит значение анонимного типа с парой полей типа Int и String (тоже выведены). Другая функция использует данные из такой записи. Напрямую одна другую даже не вызывает. Все компиляется и работает гладко. Теперь если заменить строчку Read more... )

Senses lie

Mar. 4th, 2011 01:06 pm
thedeemon: (Default)
Если пристально смотреть на один из желтых кружков, другие желтые исчезают.



исходник

AVM2

Feb. 16th, 2011 11:56 pm
thedeemon: (Default)
Сейчас доделываем Flash-based проигрыватель для видео, сжатого лучшим в мире кодеком для экранного видео. Для него как раз делался парсер из предыдущего поста. Декодер был переведен с С++ на haXe. Тот компилирует в байткод AVM2 - виртуальной машины, используемой в 9 и 10-м флэше. Дальше ВМ его на клиенте уже JIT'ит и выполняет. Причем байткод имеет стековую ОО семантику (а-ля JVM) с минимумом типизации (например, для сложения есть отдельные команды для интов, а для сравнения уже нет, только общие, рассчитанные на определение типа в рантайме). JIT'у приходится типы выводить. Сегодня немного посравнивал скорость и впечатлился: декодер на haXe & Flash player отличается по скорости от C++ & Intel compiler всего процентов на 20. А вы говорите флэш тормозит.

Демку проигрывателя можно пощупать тут:
http://data.infognition.com/sp_demo/

Перевести кодек оказалось довольно простой задачей, гораздо сложнее было организовать логику плеера с прыганием на произвольные позиции, декодированием про запас и обеспечением плавности отображения (для чего пришлось в одном месте делать что-то вроде копроцедур и кооперативной многозадачности, ибо настоящих потоков нет).

Флэшка с декодером, парсером и логикой плеера сейчас занимает меньше 20 КБ. Осталось прикрутить звук и сделать управление посимпатичней.
thedeemon: (Default)
В текущем проекте на haXe нужно читать из сети AVI файл и по мере его загрузки разбирать и декодировать. Для начала для простых тестовых файлов был сделан простой разбор в лоб, но быстро стало понятно, что такой подход не масштабируется - объем кода быстро растет, а логика по нему сильно размазывается. Захотелось декларативного описания разбираемого формата, вспомнил про парсер-канабинакомбинаторы. Но они работают там, где весь необходимый инпут уже есть, для загружаемого постепенно нужно их готовить по-другому. Посмотрел было на проект reactive parsing (RxParsers), но там под тоннами бойлерплейта найти здравую мысль оказалось очень сложно, а автор честно признался, что парсер-комбинаторы видел впервые и как работает его проект сам толком не понимает. В общем, сделал я свое решение, простое и удобное.
Read more... )

Profile

thedeemon: (Default)
Dmitry Popov

May 2025

S M T W T F S
    123
45678910
11 121314151617
18192021222324
25262728293031

Syndicate

RSS Atom

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jun. 10th, 2025 06:53 am
Powered by Dreamwidth Studios