Недавно команда использует WASM + FFmpeg для создания WEB-плеера. Мы декодируем видео с помощью FFmpeg, используя язык C, и взаимодействуем с помощью JavaScript, компилируя язык C в WASM, работающий в браузере. По умолчанию данные, декодированные FFmpeg, имеют формат yuv, а canvas поддерживает только рендеринг rgb. Тогда у нас есть два способа справиться с этим yuv. Первый использует метод, предоставленный FFmpeg, для прямого преобразования yuv в rgb и последующего рендеринга в холст. Два используют webgl для преобразования yuv в rgb и рендеринга на холсте. Первое преимущество заключается в том, что метод записи очень прост. Требуется только метод, предоставленный FFmpeg, для прямого преобразования yuv в rgb. Недостатком является то, что он будет потреблять определенное количество ресурсов ЦП. Второе преимущество заключается в том, что он будет использовать GPU для ускорение Знаком с WEBGL. Учитывая, что для снижения нагрузки на CPU и использования GPU для параллельного ускорения мы выбрали второй способ.
Прежде чем говорить о YUV, давайте посмотрим, как получается YUV:
Так как мы пишем плеер, то шаги по реализации плеера должны проходить через следующие этапы:
Демультиплексирование видеофайлов, таких как mp4, avi, flv и т. д., mp4, avi, flv эквивалентно контейнеру, который содержит некоторую информацию, такую как сжатое видео, сжатое аудио и т. д., и извлекает сжатие из контейнера. сжатое видео обычно имеет формат H265, H264 или другие форматы, а сжатое аудио обычно имеет формат aac или mp3.
Сжатое видео и сжатое аудио декодируются соответственно для получения исходного видео и аудио.Исходные аудиоданные обычно имеют формат pcm, а исходные видеоданные обычно имеют формат yuv или rgb.
Затем выполните синхронизацию аудио и видео.
После того, как вы сможете просмотреть декодированные сжатые видеоданные, вы, как правило, получите yuv.
YUV
Что такое ЮВ
Для фронтенд-разработчиков YUV на самом деле немного незнаком.Те, кто занимался аудио- и видеоразработкой, обычно с этим соприкасаются.Говоря простым языком, YUV похож на привычный RGB, оба из которых цветные. - закодированы, но их три буквы Значение представления отличается от RGB. «Y» YUV представляет яркость (яркость или яркость), которая является значением серого, а «U» и «V» представляют цветность (Chrominance или Chroma), который описывает цвет изображения, и насыщенность, которые определяют цвет пикселя.
Чтобы у всех было более интуитивное представление о YUV, давайте посмотрим, как выглядят по отдельности Y, U и V. Здесь команда FFmpeg используется для преобразования изображения Наруто Учиха Итачи в YUV420P:
существуетGLYUVPlayоткрыть в программеtest.yuv, показывая исходное изображение:
Компонент Y отображается отдельно:Компонент U отображается отдельно:Компонент V отображается отдельно:Из приведенного выше видно, что полное изображение может отображаться, когда Y отображается отдельно, но изображение серое. А U и V представляют цветность, один голубоватый, а другой красноватый.
Преимущества использования ЮВ
Из того, что вы только что видели, только Y отображает черно-белое изображение, поэтому формат YUV очень просто преобразовать из цветного в черно-белый, и он может быть совместим со старомодными черно-белыми телевизорами.Эта функция используется на телевизионные сигналы.
Размер данных YUV обычно меньше, чем у формата RGB, что может сэкономить полосу пропускания при передаче. (Но если используется YUV444, это 24 бита, как RGB24)
выборка YUV
Распространенными образцами YUV являются YUV444, YUV422, YUV420:
Примечание. Черная точка представляет собой компонент Y пикселя, из которого производится выборка, а пустой кружок представляет компонент UV пикселя.
Выборка YUV 4:4:4, каждый Y соответствует набору компонентов UV.
Выборка YUV 4:2:2, каждые два Y разделяют набор компонентов UV.
Выборка YUV 4:2:0, каждые четыре Y разделяют набор компонентов UV.
Метод хранения YUV
Существует два типа форматов хранения YUV: упакованный и планарный:
В упакованном формате YUV Y, U, V каждого пикселя сохраняются непрерывно и чередуются.
Плоский формат YUV хранит Y всех пикселей последовательно, затем U всех пикселей, а затем V всех пикселей.
Например, для планарного режима YUV может хранить YYYYUUVV следующим образом, а для упакованного режима YUV может хранить YUYVYUYV следующим образом.
Обычно существует много форматов YUV, YUV420SP, YUV420P, YUV422P, YUV422SP и т. д. Давайте рассмотрим более распространенные форматы:
YUV420P (каждые четыре Y используют набор UV-компонентов):
YUV420SP (упакованный, каждые четыре Y будут иметь общий набор компонентов UV, а YUV420P отличается тем, что при сохранении YUV420SP U и V чередуются):
YUV422P (плоский, каждые два Y разделяют набор компонентов UV, поэтому U и V будут иметь на одну строку больше, чем YUV420P U и V):
YUV422SP (в упаковке, каждые два Y делят набор УФ-деталей):
Среди них YUV420P и YUV420SP можно разделить на 2 формата в соответствии с порядком U и V:
YUV420P: U до V послеYUV420P,Также известен какI420, V перед U, звонокYV12.
YUV420SP: U передняя V задняя ставкаNV12, V до U после ставкиNV21.
Данные расположены следующим образом:
I420: YYYYYYYY UU VV =>YUV420P
YV12: YYYYYYYY VV UU =>YUV420P
NV12: YYYYYYYY UV UV =>YUV420SP
NV21: YYYYYYYY VU VU =>YUV420SP
Насчет почему столько форматов, то после долгих поисков выяснилось, что причина в адаптации к разным системам ТВ вещания и системам оборудования, типа только этот режим под iosNV12, режим Android естьNV21,НапримерYUV411,YUV420Формат более распространен в данных цифровых камер, первый используется дляNTSCсистема, последняя используется дляPALсистема. Что касается внедрения системы телевещания, мы можем прочитать эту статью[Стандарт] Введение в NTSC, PAL, SECAM
Метод расчета ЮВ
Взяв в качестве примера YUV420P для хранения изображения 1080 x 1280, размер его хранилища составляет((1080 x 1280 x 3) >> 1)байт, как это считается? Давайте посмотрим на картинку ниже:
В хранилище Y420P размер Y равенW x H = 1080x1280, У есть(W/2) * (H/2)= (W*H)/4 = (1080x1280)/4, аналогично V есть(W*H)/4 = (1080x1280)/4, так что картинкаY+U+V = (1080x1280)*3/2.
Поскольку все три части являются хранилищем в начале строки, а Y, U, V сохраняются последовательно между тремя частями, то место хранения YUV будет следующим (PS: будет использоваться позже):
Проще говоря, WebGL — это технология, используемая для рисования и рендеринга сложной 3D-графики на веб-страницах и позволяющая пользователям взаимодействовать с ними.
WEBGL-композиция
В мире webgl основными графическими элементами, которые можно рисовать, являются только точки, линии и треугольники.Каждое изображение состоит из больших и малых треугольников, как показано на рисунке ниже.Какой бы сложной ни была графика, основные компоненты состоят из треугольников...
шейдер
Шейдеры — это программы, работающие на графическом процессоре и написанные на языке шейдеров OpenGL ES, который чем-то похож на язык C:
Для рисования графики в WEBGL необходимо иметь два шейдера:
вершинный шейдер
фрагментный шейдер
При этом основная функция вершинного шейдера используется для обработки вершин и фрагментов шейдера используется для обработки каждого фрагмента, генерируемого этапом растирации (PS: фрагмент, можно понять как пиксель), и, наконец, рассчитывается для каждого цвета пикселя.
Процесс рисования WEBGL
1. Укажите координаты вершинТак как программа тупая и не знает вершины графа, нам нужно предоставить ее самим, координаты вершин можно прописать вручную или экспортировать программно:
В этом графе мы записываем вершины в буфер, объект буфера — это область памяти в системе WebGL, мы можем за один раз заполнить большое количество данных вершин в объект буфера, а затем сохранить данные в нем, для использование вершинными шейдерами. Затем мы создаем и компилируем вершинные и фрагментные шейдеры и используем программу для соединения двух шейдеров и их использования. Например, чтобы понять, почему это делается, мы можем понимать это как создание элемента Fragment:let f = document.createDocumentFragment(),
После того, как все шейдеры будут созданы и скомпилированы, они будут в свободном состоянии, нам нужно их подключить и использовать (что можно понимать какdocument.body.appendChild(f), добавлены в тело, элементы dom видно, то есть контактировать и пользоваться).
Затем нам также нужно буферизировать и подключить вершинные шейдеры, чтобы они вступили в силу.
2. Элемент сборкиПосле того, как мы предоставим вершины, графический процессор выполнит программу вершинного шейдера одну за другой в соответствии с количеством предоставленных нами вершин, сгенерирует окончательные координаты вершин и соберет граф. Можно понять, что для изготовления воздушного змея нужно сначала построить каркас воздушного змея, а сборка примитивов находится на этом этапе.
3. РастеризацияЭтот этап похож на изготовление воздушного змея.После создания скелета воздушного змея он не может летать в это время, потому что внутри пусто, и к скелету нужно добавить ткань. На данном этапе происходит растеризация, преобразование собранной геометрии примитивов во фрагменты (PS: под фрагментами можно понимать пиксели).
4. Затенение и рендеринг
Этот этап раскрашивания является как бы завершением построения полотна змея, но узора в это время нет, а рисунок нужно нарисовать, чтобы змей стал красивее, то есть растеризованная графика на данном этапе не имеет цвета. время, и должен быть обработан фрагментным шейдером.Фрагменты раскрашиваются и записываются в цветовой буфер, и, наконец, геометрия с изображением может отображаться в браузере.
СуммироватьПроцесс рисования WEBGL можно обобщить следующим образом:
Укажите координаты вершины (требуется, чтобы мы предоставили)
Сборка элементов (собрана в графику по типу элемента)
Растеризация (собранная графика с примитивами для генерации пикселей)
Укажите значения цвета (можно рассчитать динамически, окраска пикселей)
Рисование в браузере через холст.
Идеи для рисования WEBGL YUV
Так как изображение каждого видеокадра разное, мы не должны знать столько вершин, так как же нам нарисовать изображение видеокадра с помощью webgl? Здесь используется хитрость — наложение текстуры. Проще говоря, это наклеивание изображения на поверхность геометрической фигуры, чтобы геометрическая фигура выглядела как геометрическая фигура с изображением, то есть взаимно-однозначное соответствие между координатами текстуры и координатами системы webgl:
Как показано на рисунке выше, это координата текстуры, которая делится на координаты s и t (или координаты uv), диапазон значений находится между [0, 1], и значение не имеет ничего общего с изображением. размер и разрешение. На картинке ниже представлена система координат webgl, которая представляет собой трехмерную систему координат. Здесь объявлены четыре вершины, и два треугольника используются для формирования прямоугольника, а затем вершины текстурных координат соответствуют системе координат webgl один -к одному и, наконец, переданный шейдеру фрагментов, шейдер фрагментов извлекает цвет каждого тексела изображения, выводит его в цветовой буфер и, наконец, отрисовывает в браузере (PS: тексели можно понимать как пиксели, которые составить текстурное изображение). Однако, если взаимно однозначное соответствие выполняется по рисунку, отображение будет обратным, т.к. координаты изображения холста по умолчанию (0, 0) находятся в верхнем левом углу:
Координаты текстуры находятся в левом нижнем углу, поэтому при отрисовке изображение будет перевернутым.Решений два:
Чтобы перевернуть ось Y изображения текстуры, webgl предоставляет API:
Координаты текстуры и отображение координат webgl меняются местами, например, как показано на рисунке выше, исходные координаты текстуры(0.0,1.0)Соответствует координатам webgl(-1.0,1.0,0.0),(0.0,0.0)соответствует(-1.0,-1.0,0.0), то мы обращаем его,(0.0,1.0)соответствует(-1.0,-1.0,0.0),а также(0.0,0.0)соответствует(-1.0,1.0,0.0), так что изображение в браузере не будет инвертировано.
Создайте и скомпилируйте шейдер, подключите вершинный шейдер и фрагментный шейдер к программе и используйте:
let vertexShader=this._compileShader(vertexShaderSource,gl.VERTEX_SHADER);// 创建并编译顶点着色器
let fragmentShader=this._compileShader(fragmentShaderSource,gl.FRAGMENT_SHADER);// 创建并编译片元着色器
let program=this._createProgram(vertexShader,fragmentShader);// 创建program并连接着色器
Создадим буфер, сохраним вершины и текстурные координаты (PS: Буферный объект - это область памяти в системе WebGL, мы можем за один раз залить в буферный объект большое количество вершинных данных, а затем сохранить в нем эти данные для вершин использование шейдера).
В процессе разработки мы протестировали несколько прямых трансляций. Иногда при рендеринге изображение отображается нормально, но цвет будет зеленым. После исследования выяснилось, что ширина видео для разных прямых трансляций будет разной. Например, когда якорь находится в поле Когда используется pk, ширина равна 368, ширина популярных якорей будет 720, ширина маленьких якорей будет 540, а ширина 540 будет зеленоватой. Конкретная причина в том, что webgl будет предварительно обработано, а следующие значения будут установлены на 4 по умолчанию:
// 图像预处理
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 4);
Таким образом, настройка по умолчанию будет обрабатывать 4 байта и 4 байта на строку, а ширина каждой строки компонента Y равна 540, что кратно 4. Байты выровнены, поэтому изображение может отображаться нормально, а ширина компонентов U и V равна540 / 2 = 270, 270 не кратно 4, байты не выровнены, поэтому пигмент будет казаться зеленоватым. В настоящее время существует два способа решения этой проблемы:
Во-первых, разрешить webgl напрямую обрабатывать 1 байт на строку (что влияет на производительность):
// 图像预处理
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
Второй - сделать ширину полученного изображения кратной 8, чтобы можно было добиться выравнивания по байтам YUV, и не отображался зеленый экран, но делать так не рекомендуется.