Pull to refresh

HLS в MP4 с помощью ffmpeg в браузере

Reading time4 min
Views8.1K
Привет! Уже более двух месяцев я в свободное время пилю веб приложение для преобразования HLS и DASH в MP4, используя emscripten и ffmpeg, от чего хочется поделиться тем, как у меня это получилось сделать.

В этой статье я не буду приводить исходный код правок и патчей ffmpeg, т.к. большая их часть была сделана на коленке, а я не очень хорош в C. Но сейчас достаточно статей которые вам помогут.

Введение


Два года назад у меня была цель объединить аудио и видео дорожку вместе в один mp4 файл. Тогда я только погружался в emscripten и для основы нашел репозиторий ffmpeg.js из которого очень много чего почерпнул. Тогда мне почти удалось достичь цели, хотя я очень условно ориентировался в C.
Разбираясь в исходниках ffmpeg сделал патч для работы с файловой системой, где чтение из файла вызывало асинхронную функцию в js из которой я читал blob файла и передавал буфер, а при записи вызывалась js функция которая отправляла данные буфера в хранилище.

Но была проблема с асинхронными функциями, которую я не мог корректно решить, они работали через asyncify (fastcomp), который работал не правильно в некоторых случаях, а именно выполнение кода в wasm не приостанавливалось, от чего не дождавшись получения результата от js функции — все ломалось. Эта проблема чинилась через флаг EMTERPRETIFY_WHITELIST, который судя по всему переносил код из wasm в asm заодно и замедляя его, и нужно было при каждом исключении отлаживать стек вызовов и добавлять сломанную функцию в список.

В общем с такими проблемами это нельзя было назвать рабочим решением, на чем все это и осталось небольшим демо.



Полтора года спустя


После просмотра доклада на Google Dev Summit о новый фитчах в WebAssambly, Я пошел посмотреть как там дела у emscripten, и увидел сообщение:
Emscripten emits WebAssembly using the upstream LLVM wasm backend, since version 1.39.0 (October 2019), and the old fastcomp backend is deprecated

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

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

Здесь я вроде бы достиг своей цели, но… появилась новая.

Переписать HTTP протокол


Такая мысль у меня витала давно. Это может позволить загрузить HLS или DASH, причем не только готовый плейлист но и live поток. А чего то подобного я на просторах интернета еще не встречал.

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

Еще пара недель и наконец у меня получилось сделать первую итерацию рабочего http протокола, и здесь казалось бы все?

Когда самое сложное уже позади


На этот момент у меня была готова основа, небольшая html форма с полем ввода url и кнопкой старт, базово оно работало. Но еще требовалось написать расширение чтобы обходить CORS и загружать данные, сделать хранилище которое будет чанками писать данные, сделать интерфейс с отображением прогресса, все это отладить в починить проблемы в разных браузерах. В общем пришло время наконец сделать так, чтобы этим можно было пользоваться.

Базово был сделан userscript, который представлял из себя прокси для fetch запросов от ffmpeg, чтобы скачивать данные.

Еще пара дней и было готово расширение для Chrome и Firefox, которое используя webRequest собирало все hls ссылки которые браузер загружает при просмотре видео.

В Firefox как оказалось API расширений не позволяет управлять питанием, от чего нельзя запретить засыпание компьютера, увы.

Расширение выглядит так:



Так же немного улучшил страничку на которой был сайт, прикрутил material-ui, доработал все места что были сделаны на скорую руку.



После тестирования разных способов хранения данных, выявил ряд проблем:

Blob — Chrome пишет их в оперативную память и скидывает на диск при переполнении, вот только в OSX при переполнении памяти операционка выходит из акка и закрывает все приложения которые были открыта. А Firefox вообще всегда держал данные в памяти.

Cache storage — работает как и IndexedDb, но после записи данных, в Chrome blob остаются в оперативной памяти (то ли баг то ли фитча), но получается так, что пишутся данные в cache storage (на диск) а при переполнении памяти еще и скидывает тот же объем на диск в виде blob.

IndexedDb — работает как часы, умеет хранить blob, пишет данные без излишеств, но Firefox жестко ограничивает объем 2gb.

Понемногу дорабатывал, удалось сделать функцию прерывания процесса ffmpeg (через указатель), придумал как сделать выбор форматов (ffprobe) и обработку ошибок сети.





И вот, результат вы можете сами опробовать здесь

Для меня это незаменимая вещь, когда нужно записать стрим на твиче или скачать VOD. Так же работает с юутбом, mixer, и любыми другими сайтами которые транслируют контент в HLS или DASH (увы реализация DASH в ffmpeg очень условная и live может скачивать не корректно, т.к. не учитывает интервал запроса фрагмента).

Спасибо что дочитали!

Если у вас есть вопросы по ffmpeg и emscripten — пишите, постараюсь ответить.
Tags:
Hubs:
+7
Comments6

Articles

Change theme settings