Декодирование видео с помощью FFmpeg

Декодирование видео с помощью FFmpeg

Введение

Статья является переработанной версией предыдущей статьи о декодировании видео c помощью FFmpeg. Со времени написания первой статьи некоторые функции устарели и появились новые. В данной статье обновлён пример и исходных код. Также вы можете прочитать статью Запись видеофайла с помощью ffmpeg, в которой рассказывается как создать свой видеофайл с видео и аудио дорожками.

При написании мультимедийных программ часто возникает необходимость декодирования (чтения) видеофайлов. Предположим, вам необходимо написать мультимедиа плеер или проиграть видеоролик в игре. Решение задачи должно быть максимально кроссплатформенным. Для решения данной задачи идеально подходит FFmpeg.

О библиотеке FFmpeg

FFmpeg - кроссплатформенная библиотека, созданная для декодирования и кодирования мультимедийных файлов. Библиотека имеет открытый исходный код и распространяется под лицензией GPL и LGPL. Следовательно, библиотеку можно использовать в коммерческих проектах.

FFmpeg используется в таких проектах как: ffmpeg2theora, VLC, MPlayer, Handbrake, Blender, Google Chrome и многих других.

Подготовка FFmpeg

Для использования FFmpeg необходимо проделать подготовительные действия. Есть несколько путей подготовки FFmpeg:

Первый из них - это скачивание исходных файлов ffmpeg-а и компиляцию их с помощью gcc. Компиляция может быть утомительной, но с другой стороны, вы можете точно знать с какими параметрами скомпилированы библиотеки.

Второй путь, который выбрал автор этой статьи, это скачивание сторонних билдов, их можно найти на сайте: http://ffmpeg.zeranoe.com/. В нашей статье мы использовали следующие библиотеки FFmpeg: avcodec, avdevice, avformat, avutil, swscale.

Lib файлы и h-файлы вы можете найти в Dev версии, а dll Shared в версии.

Для подключения h-файлов ffmpeg необходимо их обернуть:

extern "C"
{
	#include "avcodec.h"
}

Теперь для использования FFmpeg у нас есть все, перейдём к декодированию. Забегая вперёд скажу, что в конце статьи вы сможете найти пример, где все эти действия уже проделаны.

Кратко о видеофайлах

Видеофайлы могут иметь различные форматы, например avi, wmv, ogg. Формат - это контейнер, который определяет внутреннюю структуру файла. Видеофайл можно представить в следующем виде:

 Структура видео файла

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

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

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

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

Декодирование видеофайлов с помощью FFmpeg

Декодирование файла можно разбить на несколько шагов:

  • Шаг 0: Инициализация FFmpeg
  • Шаг 1: Открытие файла
  • Шаг 2: Поиск потоков и открытие декодеров
  • Шаг 3: Получение информации о потоках
  • Шаг 4: Декодирование информации
  • Шаг 5: Обработка кадра
  • Шаг 6: Закрытие файла

Шаги 4 и 5 повторяются для каждого кадра. Теперь разберем каждый шаг отдельно:

Шаг 0: Инициализация FFmpeg

// Регистрируем все компоненты FFmpeg
av_register_all();

Шаг 1: Открытие файла

Открываем файл и получаем его контекст. По имени файла мы открываем его и проверяем, что файл открылся успешно:

// Открываем файл
if (av_open_input_file(&pFormatCtx, inputFile.c_str(), NULL, 0, NULL) != 0)
{
  CloseFile();
  return false;
}
// Получаем информацию о потоках
if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
{
  CloseFile();
  return false;
}

Шаг 2: Поиск потоков и открытие декодеров

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

Поиск видеопотока и открытие декодера:

// номер видеопотока
videoStreamIndex = -1;
for (unsigned int i = 0; i < pFormatCtx->nb_streams; i++)
{
  if (pFormatCtx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO)
  {
    videoStreamIndex = i;
    pVideoCodecCtx = pFormatCtx->streams[i]->codec;
    // ищем декодер
    pVideoCodec = avcodec_find_decoder(pVideoCodecCtx->codec_id);
    if (pVideoCodec)
    {
      // открываем декодер
      res     = !(avcodec_open2(pVideoCodecCtx, pVideoCodec, NULL) < 0);
      width   = pVideoCodecCtx->coded_width;
      height  = pVideoCodecCtx->coded_height;
    }
    break;
  }
}

Поиск аудиопотока и открытие декодера отличается от поиска потока видео только константой CODEC_TYPE_AUDIO.

Шаг 3: Получение информации о потоках

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

Получение информации о видеопотоке:

// Кадры в секунду.
videoFramePerSecond = av_q2d(pFormatCtx->streams[videoStreamIndex]->r_frame_rate);
// базовая единица времени
videoBaseTime       = av_q2d(pFormatCtx->streams[videoStreamIndex]->time_base);
// длина видео ролика
videoDuration       = (unsigned long) pFormatCtx->streams[videoStreamIndex]->duration * (videoFramePerSecond * videoBaseTime);
// ширина
width  = formatCtx->streams[videoStreamIndex]->codec->width;
// высота
height  = formatCtx->streams[videoStreamIndex]->codec->height;

Получение информации о аудиопотоке очень похоже. Только высоты и ширины у него нет.

Шаг 4: Декодирование информации

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

Время ffmpeg представлено во внутренних единицах, так называемый BaseTime. Для преобразования времени в секунды необходимо время ffmepg-а умножить на BaseTime. Соответственно, для преобразования времени в секундах во время ffmpeg, необходимо разделить на BaseTime. Для каждого потока BaseTime индивидуально.

Для чтения пакета используется функция:

while (av_read_frame(pFormatCtx, &packet) >= 0)

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

Принадлежит ли пакет к видеопотоку можно узнать следующим образом:

if(packet.stream_index == videoStreamIndex)

где videoStreamIndex индекс видеопотока.

Декодирование кадра:

// Декодирования пакета в кадр.
AVFrame * pOutFrame;
int videoFrameBytes = avcodec_decode_video2(pVideoCodecCtx, pOutFrame, &got_picture_ptr, avpkt);

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

Для декодирования звука используется функция:

int packetDecodedSize = avcodec_decode_audio4(pAudioCodecCtx, audioFrame, &got_picture_ptr, avpkt);

Оцифрованный звук представляет собой точки дискретизации. Их формат можно узнать из структуры информации о звуковом потоке. Например, для формата AV_SAMPLE_FMT_FLTP, каждый семпл представляет из себя float со значениями от -1.0 до 1.0 и каждый поток находится в отбельной плоскости. Указатели на каждую плоскость находится в поле audioFrame->extended_data[i]. В примере вы сможете найти код как преобразовать звук из формата AV_SAMPLE_FMT_FLTP в формат AV_SAMPLE_FMT_S16.

Шаг 5: Обработка кадра

Видеокадры могут иметь разные форматы: RGB, YUV, YUVP. Чаще всего это YUV формат. Скорее всего, кадры буду обрабатываться в формате RGB, и необходимо преобразовать кадры в RGB формат. Для этого можно использовать библиотеку swscale, она входит в состав FFmpeg. Ниже приведён пример использования:

// создание контекста для преобразования
pImgConvertCtx = sws_getContext(pVideoCodecCtx->width,
  pVideoCodecCtx->height,
  pVideoCodecCtx->pix_fmt,
  pVideoCodecCtx->width, pVideoCodecCtx->height,
  PIX_FMT_BGR24,
  SWS_BICUBIC, NULL, NULL, NULL);
// преобразование кадра
sws_scale(pImgConvertCtx, pFrameYuv->data, pFrameYuv->linesize,
          0, height, frame->data, frame->linesize);

Шаг 6: Закрытие файла

При закрытии файла высвобождаются ресурсы. Ниже приведён код закрытия файла и высвобождение ресурсов:

// закрытие видео кодека
avcodec_close(pVideoCodecCtx);
// закрытие аудио кодека
avcodec_close(pAudioCodecCtx);
// закрытия контекста файла
av_close_input_file(pFormatCtx);

Заключение

Из статьи вы могли узнать, как использовать FFmpeg. Стоит отметит, что FFmpeg поддерживает большое количество форматов и кодеков.

Пример

Да, без примера статья была бы не закончена. Вы можете скачать пример использования ffmpeg. Пример не имеет полную функциональность, а только демонстрирует основы работы с FFmpeg. Программа открывает файл и сохраняет на диск 50 первых кадров. Вы можете её настроить, изменив define-ы:

#define FILE_NAME          "C:\\temp\\test.avi"
#define OUTPUT_FILE_PREFIX "c:\\temp\\image%d.bmp"
#define FRAME_COUNT        50

Стоит отметить, чтобы проект работал в Release конфигурации, необходимо включить опцию: "References: Keep Unreferenced Data (/OPT:NOREF)" или прописать параметр /OPT:NOREF в командной строке.

Ссылки

http://ffmpeg.org/ - официальный сайт проекта FFmpeg.

http://en.wikipedia.org/wiki/FFmpeg - о FFmpeg на Википедии.

http://ffmpeg.zeranoe.com/ - проект FFmpeg для Windows.

http://dranger.com/ffmpeg/ - видеоплеер на 1000 строк. Об использовании FFmpeg подробнее.

http://unick-soft.ru/Articles.cgi?id=18 - предыдущая статья.

Теги: C++ Звук Видео FFmpeg