Всем привет! Сегодня я хочу открыть
серию статей по изучению FFmpeg libav с нуля.

Сразу уточню, что в основном статьи направлены на программирование, используя библиотеки libav*, где в качестве языка выступит С++.

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

Знакомство (небольшая предыстория)

Несколько месяцев назад у меня появилась необходимость познакомиться с основами работы с аудио и видео файлами посредством языка С++ и библиотек libav*. Под libav* я подразумеваю libavformat, libavcodec, libavutil и другие.

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

Важные основы

Перед началом чтения моих статей, настоятельно прошу ознакомиться с основами, которые освещаются здесь.

Введение в FFmpeg

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

Основные преимущества FFmpeg:

  • Открытый исходный код, удобный для ознакомления и модификации;

  • Кроссплатформенность;

  • В большинстве случаев редактирование медиа файлов выполняется без потери качества;

  • Поддержка огромного количества кодеков для различных форматов медиа файлов.

Мы же будем использовать библиотеки FFmpeg (libav*) для разработки на С++, подробнее обо всём можно почитать на официальном сайте (промотайте в самый низ, если вас интересует список библиотек и их предназначение).

Подготовка рабочего окружения (по желанию)

Данный раздел предназначен для каждого, кого интересует простая настройка рабочего окружения на Windows. Я пользуюсь редактором кода Visual Studio Code, менеджером пакетов MSYS2 и компилятором GCC из группы компиляторов mingw-w64.

  1. Скачиваем и устанавливаем менеджер пакетов с официального сайта.

  2. Запускаем MSYS2 и обновляем основные системные пакеты: $ pacman -Syu

  3. Перезапускаем MSYS2 и обновляем остальные пакеты: $ pacman -Su

  4. Устанавливаем mingw-w64 и некоторые вспомогательные пакеты (make, pkgconf):
    $ pacman -S --needed base-devel mingw-w64-x86_64-toolchain

  5. Устанавливаем пакет FFmpeg:
    $ pacman -S mingw-w64-x86_64-ffmpeg

  6. Настраиваем Visual Studio Code. По его настройке довольно много информации на официальном сайте, поэтому покажу лишь основное:

    1. Добавить путь к заголовочным файлам (FFmpeg libav*) в "includePath" .
    2. Добавить путь к компилятору (mingw-w64 gcc) в "compilerPath".

    c_cpp_properties.json
    c_cpp_properties.json

Начало работы с libav*

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

Полный код программы можно найти здесь.

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

int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        av_log(NULL, AV_LOG_ERROR, "Usage: <input file>\n");
        return ARGUMENTS_ERROR;
    }
    const char* input_file = argv[1];

    return EXIT_SUCCESS;
}

Далее прочитаем файловый заголовок и сохраним информацию о найденных форматах в структуру AVFormatContext . Для удобства все манипуляции будут производиться в отдельной функции.

Подробнее ознакомиться с AVFormatContext и его полями можно на официальном сайте.

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

int open_input_media_file(AVFormatContext** format_context, const char* input_file)
{
    int response;

    *format_context = avformat_alloc_context();
    if(!(*format_context))
    {
        av_log(NULL, AV_LOG_ERROR, "Could not allocate memory for the input format context.\n");
        return ALLOCATE_FORMAT_CONTEXT_ERROR;
    }

    response = avformat_open_input(format_context, input_file, NULL, NULL);
    if(response != 0)
    {
        av_log(NULL, AV_LOG_ERROR, "Could not open the input file: %s.\n", input_file);
        return OPEN_INPUT_FILE_ERROR;
    }

    response = avformat_find_stream_info(*format_context, NULL);
    if(response < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "Could not find the input stream information.\n");
        return FIND_STREAM_INFO_ERROR;
    }

    return EXIT_SUCCESS;
}

Для вывода информации можно воспользоваться стандартной функцией av_dump_format() , которая принимает структуру AVFormatContext в качестве первого входного параметра.

Подробнее ознакомиться с av_dump_format() и её параметрами можно на официальном сайте.

Осталось инициализировать структуру AVFormatContext и добавить вызов ранее созданной функции open_input_media_file() в main() .

Ну и, конечно же, не стоит забывать про очистку памяти. Для этого мы напишем отдельную функцию clean_memory() и также добавим её вызов в main() .

void clean_memory(AVFormatContext* format_context)
{
    if(format_context != NULL)
    {
        avformat_close_input(&format_context);
        avformat_free_context(format_context);
    }
}
int main(int argc, char* argv[])
{
  	AVFormatContext* format_context = NULL;

    if(argc != 2)
    {
        av_log(NULL, AV_LOG_ERROR, "Usage: <input file>\n");
        return ARGUMENTS_ERROR;
    }
    const char* input_file = argv[1];

    int response = open_input_media_file(&format_context, input_file);
    if(response != EXIT_SUCCESS)
    {
      	clean_memory(format_context);
        return response;
    }

    av_dump_format(format_context, 0, input_file, 0);

    clean_memory(format_context);
    return EXIT_SUCCESS;
}

Сборку и запуск программы можно осуществить как с помощью командной строки:
g++ 1.cpp -o out -lavformat -lavutil , так и с использованием Makefile (об этом довольно много информации в интернете).

После запуска программы мы увидим информацию, полученную из нашего медиафайла, обо всех его потоках.

av_dump_format
av_dump_format

Заключение

В рамках данной статьи мы немного познакомились с libav* и написали вводный пример. Далее будем переходить к более реальным вещам, например, декодированию, транскодированию, использованию фильтров и многому другому.

Комментарии (12)


  1. netricks
    04.01.2022 09:31
    +4

    Статье очень не хватает введения. Что это за библиотека, что она делает и нафига нужна прогрессивному человечеству.


    1. gmeyster Автор
      04.01.2022 10:29
      +1

      Добрый день, хорошо, я это исправлю, спасибо за отзыв.


  1. freedbrt
    04.01.2022 10:04

    Действительно библиотека довольно неплохая, но в ней есть большой минус что она работает только на CPU, в то время когда в современном мире уже практически на любой платформе есть специализированные ядра GPU для этих целей.

    Возможно кто нибудь подскажет альтернативу ей, но с возможностью работоты на GPU?


    1. Chaa
      04.01.2022 10:38
      +6

      FFmpeg умеет в Direct3D, NVIDIA CUDA, OpenCL и еще несколько менее известных. Подробнее:
      https://trac.ffmpeg.org/wiki/HWAccelIntro


      1. freedbrt
        04.01.2022 11:12

        Спасибо, действительно, перепутал с другой библиотекой.


  1. BiOM
    04.01.2022 10:41

    Огромное спасибо за написание статьи. Надеюсь что в итоге выйдет цикл статей, достаточно полно покрывающий функционал FFmpeg. Лично у меня при работе с этой библиотекой вылезла проблема, которую я так и не смог решить. Необходимо ускорить воспроизведение ауди при этом сохраняя тональность звук. К сожалению все мои эксперименты завершились неудачей. Был бы рад, если кто-нибудь сможет мне дать толчек в нужном направлении.


    1. NightShad0w
      04.01.2022 12:11
      +1

      Фильтр atempo может сделать то, что требуется, мне кажется.


    1. gur_yan_off
      04.01.2022 22:40
      +1

      В ffmpeg этого можно добиться комбинацией atempo, aresample и asetrate.

      У меня была похожая надобность, только в обратную сторону - изменить высоту звука без изменнения скорости. Мне с этим помог вот этот вопрос на StackOverflow: https://stackoverflow.com/questions/53374590/ffmpeg-change-tone-frequency-pitch-audio


      1. BiOM
        04.01.2022 22:49

        Мне надо именно в коде на лету эту обработку делать, а не из коммандной строки. Я пробовал фильтры использовать, но так и не вышло. Поэтому буду ждать статью про фильтры от автора и пробовать снова.


        1. mikmiknik
          06.01.2022 09:32

          А в коде вы не можете процесс создать с ffmpeg'ом? И если надо "на лету", то STDIN и STDOUT использовать в качестве input/output.

          https://stackoverflow.com/questions/45899585/pipe-input-in-to-ffmpeg-stdin


          1. BiOM
            06.01.2022 10:55

            К сожалению такой вариант мне не подойдет. Приложение для iOS, где такое запрещено. Максимум это использование в качаестве статически слинкованной библиотеки.


  1. bender1000101011
    05.01.2022 12:43

    Библеотека ffmpeg очень мощная. Написал уже несколько проектов с помощью нее. Легко была решена задача декодирования сырого потока китайских видеорегистраторов.