Декодирование видео с помощью 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 - предыдущая статья.