Недостающее звено при обработке медиа на Go

от автора

Большинство медиа-библиотек для Go рано или поздно упираются в одну и ту же проблему.

У них нет собственного декодера.

Вместо этого они полагаются на установленный в системе пакет FFmpeg, shared библиотеки, платформозависимые DLL или внешние исполняемые файлы, которые должны присутствовать на целевой системе.

На этапе разработки всё работает. Затем начинается развертывание.

И внезапно вы отлаживаете отсутствующие DLL в Windows, несовместимые версии FFmpeg в Linux, различия версий из Homebrew на macOS, образы контейнеров без нужных библиотек или продуктовые серверы, где рядовое обновление пакета неожиданно ломает обработку медиа.


Цель

Мне нужен был декодер, который:

  • Работает на чистом коде Go

  • Собирается в единый бинарник

  • Не требует установки FFmpeg или других рантайм зависимостей

  • Поддерживает современные кодеки, такие как AV1, H.264, H.265, VP8, VP9, Opus, AAC, MP3 и другие

  • Может читать из файлов, буферов памяти, HTTP-потоков, пайпов, SSH-потоков и любого Go io.Reader

Результат — gopeg.

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

Когда бинарник готов, всё необходимое для декодирования уже находится внутри.

Почему это важно

Огромное количество времени уходит на решение проблем развёртывания, а не прикладных задач. Типичный пайплайн при обработке медиа часто выглядит так:

  • Установить FFmpeg

  • Установить совместимые разделяемые библиотеки

  • Корректно их упаковать

  • Убедиться, что на продуктовых машинах стоят совместимые версии

  • Надеяться, что будущие обновления операционной системы ничего не сломают

Приложение становится зависимым от внешнего состояния. Со статической линковкой сценарий развёртывания становится гораздо проще: go build и всё.

Никаких внешних пакетов кодеков. Никаких DLL. Никакого поиска библиотек во время выполнения и кривых cmd.Exec. Без фраз — «у меня всё работает».

Поддержка io.Reader

Ещё одной целью дизайна была гибкость. Декодеру не нужен прямой доступ к файлу. Он должен уметь работать с произвольными потоками:

resp, _ := http.Get(url)dec, err := gopeg.NewDecoder(resp.Body)

Или:

dec, err := gopeg.NewDecoder(os.Stdin)

Или даже с данными, поступающими через SSH-соединение или кастомный транспорт. Из seekable источников можно извлечь длительность или другие метаданные. Для non-seekable ридеров декодирование всё равно работает нормально, видео/аудио обрабатывается по мере поступления данных.

Простота вместо абстракции

Одним из моих главных желаний было избежать создания громоздкого фреймворка. API намеренно сделан прямолинейным и простым.

Открываем источник:

dec, err := gopeg.NewDecoder(file)

Читаем метаданные:

meta := dec.Meta()

Декодируем кадры:

for !dec.HasEnded() {    frame, err := dec.DecodeFrame()    if err != nil {        panic(err)    }    // обрабатываем кадр}

Никаких фоновых воркеров. Никаких скрытых процессов. Никаких внешних бинарников. Чистый декод.

Заключение

FFmpeg — потрясающий продукт, но зависимость от системной установки FFmpeg создаёт эксплуатационную сложность, которая на самом деле не нужна многим приложениям. Для моих задач статически слинкованный подход оказался более осмысленным, также получилось выиграть в размере финальной сборки. Результат — медиа-декодер, который легко встроить, легко развернуть и который предсказуемо работает в любом окружении.

Собрал один раз. Отправляешь бинарник. Декодируешь медиа где угодно.

Загляните в проект gopeg и дайте знать, какие форматы или функции вы хотели бы увидеть следующими.

ссылка на оригинал статьи https://habr.com/ru/articles/1042916/