- PVSM.RU - https://www.pvsm.ru -
В последнее время постобработка видео в рантайме приобретает всё большее значение — благодаря мощности современных ПК, почти каждый пользователь может пропустить видеоряд через сложную цепочку фильтров прямо во время просмотра, тем самым избавляясь от необходимости полноценного кодирования видео, зачастую производимого с помощью медленных и переусложненных средств [1].
Эта область довольно неплохо покрыта в десктопной среде — фильтры вроде ffdshow raw video filter [2] и madVR [3] позволяют делать практически всё, что может потребоваться для приятного просмотра. К сожалению, веб не может похвастаться аналогичным тулкитом, и вы либо наслаждаетесь всеми недостатками очередного видео на YouTube, либо открываете его во внешнем приложении вроде MPC-BE [4], что не очень удобно. А было бы неплохо иметь одну волшебную кнопку, активирующую фильтрацию в месте, где она и должна быть — в вашем браузере.
Данный пост представляет собой краткий отчет о моих изысканиях в этой области, где конечной целью ставилась оценка возможности проведения фильтрации в режиме реального времени на разрешении минимум 1920x1080.
Во время чтения статьи следует учесть:
Единственным вариантом, который позволил бы иметь одно ядро для всех целевых браузеров (Chrome и Firefox в первую очередь) является браузерное расширение. Альтернатива в виде Google Chrome Native Client [6], внезапно, работает только в Chrome, и Mozilla на данный момент не собирается поддерживать NaCl в Firefox. Кроме того, я не изучал возможности доступа NaCl к элементам на странице — вполне может оказаться, что для наших целей он не сработает.
Базовый алгоритм работы (теоретического) расширения довольно прост: ищем элемент video на странице, прячем его, и сверху создаем canvas, на котором рендерятся фильтрованные кадры видео-потока. Пока всё просто.
Реальной проблемой у расширения является язык реализации — интерпретируемый JavaScript, а как мы знаем, интерпретируемые языки плохо подходят для серьезных расчетов. Но ведь это не беда! JavaScript последнее время получает море любви и оптимизаций, и существует довольно большое количество программистов, считающих, что JS — язык, подходящий для написания любых приложений и что вообще всё должно двигаться в веб. Более того, доступно множество новых технологий вроде asm.js, SIMD.js, WebGL и WebCL, которые, в теории, позволяют реализовывать всё, что душе угодно, со скоростью лишь немного меньше нативной. Так что мы не должны иметь никаких серьезных проблем с написанием набора фильтров в браузере, правда?
Не совсем.
Фильтрация в чистом JS работает по следующей схеме:
context.drawImage(video, 0, 0)
, где context — 2d контекст, полученный с канваса.context.getImageData(0, 0, width, height)
. context.putImageData(imageData, 0, 0)
.Этот алгоритм работает и позволяет проводить реальную фильтрацию видео в чистом JavaScript с минимальным количеством очень похожего на C кода. Так будет выглядеть базовая (не оптимизированная) реализация фильтра invert, инвертирующего RGB байты в каждом пикселе кадра:
outputContext.drawImage(video, 0, 0);
var imageData = outputContext.getImageData(0, 0, width, height);
var source = imageData.data;
var length = source.length;
for (var i = 0; i < length; i += 4) {
source[i ] = 255 - source[i];
source[i+1] = 255 - source[i+1];
source[i+2] = 255 - source[i+2];
// игнорируем альфу
}
outputContext.putImageData(imageData, 0, 0);
И хотя этот метод работает для демок и простых картинок, он очень быстро “сдувается” на высоких разрешениях. Хотя вызов drawImage
сам по себе довольно быстр даже на 1080p [7], после добавления getImageData
и putImageData
время выполнения растет до 20-30 миллисекунд на одну итерацию [8]. Полный код, приведенный выше, выполняется уже за 35-40мс [9], что является предельной скоростью для PAL-видео (25 кадров в секунду, 40мс на один кадр). Все замеры получены на 4770k, который является одним из наиболее мощных домашних процессоров на данный момент. Это означает, что выполнение любого более-менее сложного фильтра на предыдущих поколениях процессоров невозможно вне зависимости от производительности JavaScript. Любой, даже очень быстрый код, будет упираться в ужасную производительность самого канваса.
Но JavaScript не очень быстр сам по себе. Хотя обычные операции вроде инвертирования или прогона через LUT могут выполняться за разумное время, любой более-менее сложный фильтр вызывает ужасные лаги. Простая реализация фильтра добавления шума [10] (Math.random()*10 к каждому пикселю) требует уже 55 миллисекунд, а 3х3 ядро для блюра [11], реализованное в приведенном ниже коде, проходит за 400мс, или 2.5 кадров в секунду.
function blur(source, width, height) {
function blur_core(ptr, offset, stride) {
return (ptr[offset - stride - 4] +
ptr[offset - stride] +
ptr[offset - stride + 4] +
ptr[offset - 4] +
ptr[offset] +
ptr[offset + 4] +
ptr[offset + stride - 4] +
ptr[offset + stride] +
ptr[offset + stride + 4]
) / 9;
}
var stride = width * 4;
for (var y = 1; y < (height - 1); ++y) {
var offset = y * stride;
for (var x = 1; x < stride - 4; x += 4) {
source[offset] = blur_core(source, offset, stride);
source[offset + 1] = blur_core(source, offset + 1, stride);
source[offset + 2] = blur_core(source, offset + 2, stride);
offset += 4;
}
}
}
Firefox показывает еще более удручающие результаты с 800 мс/проход. Что интересно, IE11 опережает даже Chrome, причем в два раза (но сам canvas у него медленный, так что это не спасает). В любом случае, становится ясно, что чистый JavaScript — неправильное средство для реализации фильтров.
Новомодный asm.js [12] — средство от компании Mozilla для оптимизации выполнения JavaScript кода. Генерируемый код по-прежнему будет работать в хроме, однако надеяться не серьезный прирост производительности не стоит, поскольку поддержка asm.js, по всей видимости, еще не добавлена [13].
К сожалению, я не смог найти простой путь компиляции выбранных функций в asm.js-оптимизированный код. Emscripten [14] генерирует около 4.5 тысяч строк кода при компиляции простой двустрочной функции, и я не понял, как можно вытащить из него только нужный код за разумное время. Писать же asm.js руками — то ещё удовольствие [15]. В любом случае, asm.js упрётся в производительность 2d-контекста канваса, аналогично чистому JavaScript.
SIMD.js [16] — очень новая технология ручной оптимизации JS-приложений, которая в настоящий момент “поддерживается” только в Firefox Nightly [17], но очень скоро может получить поддержку всех целевых браузеров [18]. К сожалению, API сейчас работает только с двумя типами данных [19], float32x4 и uint32x4, что делает всю затею бесполезной для большинства реальных 8-битных фильтров. Более того, тип Int32x4Array пока не реализован даже в Nightly, поэтому любая запись и чтение данных из памяти будут происходить медленно и страшно (когда реализованы подобным образом [20]). Однако, приведу код реализации обычного фильтра инвертирования (на этот раз работающего через XOR):
function invert_frame_simd(source) {
var fff = SIMD.int32x4.splat(0x00FFFFFF);
var length = source.length / 4;
var int32 = new Uint32Array(source.buffer);
for (var i = 0; i < length; i += 4) {
var src = SIMD.int32x4(int32[i], int32[i+1], int32[i+2], int32[i+3]);
var dst = SIMD.int32x4.xor(src, fff);
int32[i+0] = dst.x;
int32[i+1] = dst.y;
int32[i+2] = dst.z;
int32[i+3] = dst.w;
}
}
На данный момент приведенный код выполняется значительно медленней чистого JS — 1600мс/проход (пользователи Nighly могут попробовать очередное демо [21]). Похоже, придется подождать еще достаточное количество времени, прежде чем можно будет делать хоть что-то полезное с этой технологией. К сожалению, не ясно, как будет реализована поддержка 256-битных YMM регистров (int32x4 — обычный 128-битный xmm из SSE2), и будут ли доступны инструкции из более новых технологий вроде SSSE3. Ну и SIMD.js не спасает от медленного канваса. Зато фанаты SIMD могут уже сейчас получить некоторые привычные баги [22], прямо в браузере!
Совершенно другой способ реализации фильтров — WebGL [23]. В самом базовом понимании WebGL — JS-интерфейс для нативной технологии OpenGL, которая позволяет выполнять разнообразный код на GPU. Обычно она используется для программирования графики в играх и т.п., однако никто не мешает обрабатывать картинки [24] или даже видео [25] с её помощью. WebGL также не требует вызовов getImageData, что в теории позволяет избежать типичного 20мс-лага.
Но ничто не бывает бесплатно — WebGL не является средством общего назначения и использовать это API для абстрактного неграфического кода — ужасная боль. Потребуется определять бесполезные вертексы (которые всегда будут покрывать весь кадр), правильно позиционировать текстуру (которая будет закрывать весь кадр), а затем использовать видео в качестве текстуры [26]. К счастью, WebGL достаточно умён, чтобы запрашивать нужные кадры из видео автоматом. По крайней мере, в хроме и лисе. IE11 же обрадует ошибкой WEBGL11072: INVALID_VALUE: texImage2D: This texture source is not supported
.
Наконец, для написания фильтров придётся использовать шейдеры, реализуемые на немного ущербном [27] языке GLSL, который (по крайней мере в WebGL-варианте) даже не поддерживает установку константных массивов [28], поэтому любые массивы надо будет либо передавать с помощью uniforms (такие типа-глобальные переменные), либо использовать индийский способ:
float core1[9];
core1[0] = 1.0;
core1[1] = 1.0;
core1[2] = 0.0;
core1[3] = 1.0;
core1[4] = 0.0;
core1[5] = -1.0;
core1[6] = 0.0;
core1[7] = -1.0;
core1[8] = -1.0;
Он также требует чтобы пиксельный шейдер возвращал одно значение — цвет текущего пикселя, что делает невозможной типичную реализацию некоторых фильтров, обрабатывающих несколько пикселей за итерацию (то же подавление блочности). Такие фильтры придется переосмысливать и реализовывать по-другому.
В общем, технологии вроде CUDA и OpenCL были придуманы не от хорошей жизни.
В оправдание WebGL, он имеет действительно потрясающую для веба производительность (которую вы не можете измерить [29]). По крайней мере, он может обработать фильтр prewitt из masktools [30] (выбор максимального значения из четырех 3х3 ядер) в реальном времени на 1080p [31] и выше. Если вы ненавидите себя и не боитесь получить немного неподдерживаемый код, WebGL позволяет делать с видео довольно интересные вещи. Более разумным может быть использование библиотеки seriously.js [25], которая прячет часть шаблонного WebGL-кода, однако может оказаться недостаточно продвинутой для обработки изменения разрешения видео или реализации временных фильтров.
Если же вы себя любите, то, скорее всего, вам захочется использовать что-то вроде WebCL.
Но не получится. Википедия говорит [32], что WebCL 1.0 был финализирован 19-ого марта этого года, что делает технологию самой молодой из всего списка, моложе даже SIMD.js. И, в отличие от SIMD.js, она не будет поддерживаться в Firefox в ближайшем будущем [33]. Где-то читал об аналогичном решении для Chrome, но потерял ссылку. Так что WebCL на данный момент является мёртвой технологией без ясного будущего.
Обработка видео в реальном времени в браузере возможна, однако единственный рабочий сейчас вариант реализации — использование WebGL, программирование видео-фильтров на котором — занятие, достойное настоящих мазохистов. Все остальные методы упираются в ужасную производительность 2d-контекста канваса, да и сами по себе не блещут скоростью выполнения. Такие грустные дела.
Автор: tp7
Источник [34]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/news/59516
Ссылки в тексте:
[1] медленных и переусложненных средств: http://avisynth.nl/index.php/Main_Page
[2] ffdshow raw video filter: http://sourceforge.net/projects/ffdshow-tryout/
[3] madVR: http://forum.doom9.org/showthread.php?t=146228
[4] MPC-BE: http://sourceforge.net/projects/mpcbe/
[5] по вине этих браузеров: https://stackoverflow.com/questions/17930964/video-element-with-looping-does-not-loop-videos-seamlessly-in-chrome-or-firefo
[6] Google Chrome Native Client: https://developer.chrome.com/native-client
[7] довольно быстр даже на 1080p: http://tp7.github.io/articles/javascript-video-filtering/pure.html?filter=none
[8] время выполнения растет до 20-30 миллисекунд на одну итерацию: http://tp7.github.io/articles/javascript-video-filtering/pure.html?filter=copy
[9] выполняется уже за 35-40мс: http://tp7.github.io/articles/javascript-video-filtering/pure.html?filter=invert
[10] Простая реализация фильтра добавления шума: http://tp7.github.io/articles/javascript-video-filtering/pure.html?filter=noise
[11] 3х3 ядро для блюра: http://tp7.github.io/articles/javascript-video-filtering/pure.html?filter=blur
[12] asm.js: http://asmjs.org/
[13] поддержка asm.js, по всей видимости, еще не добавлена: https://code.google.com/p/v8/issues/detail?id=2599
[14] Emscripten: https://github.com/kripken/emscripten
[15] то ещё удовольствие: http://habrahabr.ru/post/193642/
[16] SIMD.js: https://01.org/blogs/tlcounts/2014/bringing-simd-javascript
[17] Firefox Nightly: https://nightly.mozilla.org/
[18] может получить поддержку всех целевых браузеров: http://www.phoronix.com/scan.php?page=news_item&px=MTY0ODE
[19] работает только с двумя типами данных: http://www.2ality.com/2013/12/simd-js.html
[20] подобным образом: https://github.com/johnmccutchan/ecmascript_simd/blob/master/src/int32x4array.js#L107
[21] демо: http://tp7.github.io/articles/javascript-video-filtering/pure.html?filter=simd
[22] привычные баги: http://tp7.github.io/articles/javascript-video-filtering/pure.html?filter=buggySimd
[23] WebGL: https://en.wikipedia.org/wiki/WebGL
[24] картинки: http://www.html5rocks.com/en/tutorials/webgl/webgl_fundamentals/
[25] видео: http://seriouslyjs.org/
[26] использовать видео в качестве текстуры: https://developer.mozilla.org/en-US/docs/Web/WebGL/Animating_textures_in_WebGL
[27] немного ущербном: https://github.com/brianchirls/Seriously.js/blob/0d029c40401f98aea9cd6170bef7866f6c1750ac/effects/seriously.dither.js#L51
[28] не поддерживает установку константных массивов: https://stackoverflow.com/questions/15262729/const-float-array-in-webgl-shader
[29] которую вы не можете измерить: https://stackoverflow.com/questions/20798294/is-it-possible-to-measure-rendering-time-in-webgl-using-gl-finish
[30] masktools: http://manao4.free.fr/mt_masktools.html
[31] на 1080p: http://tp7.github.io/articles/javascript-video-filtering/webgl.html
[32] говорит: https://en.wikipedia.org/wiki/WebCL
[33] не будет поддерживаться в Firefox в ближайшем будущем: https://bugzilla.mozilla.org/show_bug.cgi?id=664147#c30
[34] Источник: http://habrahabr.ru/post/221619/
Нажмите здесь для печати.