Учебник по ffmpeg и SDL Урок1 Создание стопкадров

Fabrice Bellard, Martin Bohme, “An ffmpeg and SDL Tutorial Tutorial 01: Making Screencaps”, public translation into Russian from English More about this translation.

Translate into another language.

Обзор

Видеофайлы состоят из нескольки основных компонентов. Во-первых, сам файл называется контейнером, а тип контейнера определяет размещение информации в файле. Примеры контейнеров: AVI и Quicktime. Далее, у вас есть набор потоков, например, вы, как правило, имеете аудио и видео потоки. ("Поток" это образное описание "последовательности элементов данных, доступных в течение времени".) Элементы данных в потоке называются кадры. Каждый поток кодируется с помощью различных видов кодеков. Кодек определяет, как фактические данные КОдируются и ДЕКодируются - отсюда и получается КОДЕК. Примеры кодеков — DivX и MP3. Пакеты читаются из потока. Пакеты — фрагменты данных, которые могут содержать биты данных, декодируемых в несжатые кадры, которыми мы можем, наконец, управлять в нашем приложении. Для наших целей, каждый пакет содержит полные кадры, или несколько кадров в случае аудио.

Внаиболее общем виде работа с видео и аудио потоками выглядит очень просто:

10 ОТКРЫТЬ видео_поток ИЗ video.avi

20 ПРОЧИТАТЬ пакет ИЗ видео_поток В кадр

30 ЕСЛИ кадр НЕ ПОЛНЫЙ ПЕРЕЙТИ 20

40 ДЕЛАТЬ ЧТО-ТО С кадр

50 ПЕРЕЙТИ 20

Обработка мультимедиа в FFmpeg достаточно проста, как в этом псевдокоде, хотя некоторые программы могут иметь очень сложный пункт "ДЕЛАТЬ ЧТО-ТО". В этом учебнике мы собираемся открыть файл, читать данные из, содержащегося в нем, видео потока, и наш пункт "ДЕЛАТЬ ЧТО-ТО" должен быть вывести кадр в PPM файл.

Открытие файла

Во-первых, давайте сначала посмотрим, как мы откроем файл. Вы должны прежде всего инициализировать библиотеку ffmpeg. (внимание, в некоторых системах, возможно, придется использовать <ffmpeg/avcodec.h> и <ffmpeg/avformat.h>)

#include <avcodec.h>

#include <avformat.h>

...

int main(int argc, charg *argv[]) {

av_register_all();

Этот вызов регистрирует все доступные в библиотеке форматы и кодеки, поэтому они будут применяться автоматически, при открытии файл с соответствующим форматом/кодеком. Обратите внимание, что вам нужно вызвать av_register_all () всего один раз, поэтому мы его вызвали в main(). Если хотите, можно зарегистрировать только некоторые отдельные форматы и кодеки, но, обычно, нет никаких причин это делать.

Сейчас мы действительно можем открыть файл:

AVFormatContext *pFormatCtx;

// Открыть видео файл

if(av_open_input_file(&pFormatCtx, argv[1], NULL, 0, NULL)!=0)

return -1; // Не удалось открыть файл

Мы получаем имя файла из первого аргумента. Эта функция читает заголовок файла и сохраняет информацию о формате файла в переданной структуре AVFormatContext. Последнее три аргумента используются, для указания формата файла, размера буфера, и опций формата, но при установке в NULL или 0, libavformat будет определять их автоматически.

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

// Получаем информацию о потоке

if(av_find_stream_info(pFormatCtx)<0)

return -1; // Не найдена информация о потоке

Эта функция заполняет pFormatCtx-> streams соответствующей информацией. Введем удобные функции отладки, которые покажут нам, что находится внутри:

// Вывод информации о файле на stderror

dump_format(pFormatCtx, 0, argv[1], 0);

pFormatCtx->streams просто массив указателей, размером pFormatCtx->nb_streams, так что давайте найдем в нём видео поток.

int i;

AVCodecContext *pCodecCtx;

// Найти первый видеопоток

videoStream=-1;

for(i=0; i<pFormatCtx->nb_streams; i++)

if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO) {

videoStream=i;

break;

}

if(videoStream==-1)

return -1; // Не нашли видеопоток

// Получить указатель на контекст кодека видеопотока

pCodecCtx=pFormatCtx->streams[videoStream]->codec;

Информацию о кодеке потока мы называем "контекст кодека". В нем содержится вся информация о кодеке, который используется потоком. Но нам еще предстоит найти фактический кодек и открыть его:

AVCodec *pCodec;

// Найти декодер видео потока

pCodec=avcodec_find_decoder(pCodecCtx->codec_id);

if(pCodec==NULL) {

fprintf(stderr, "кодек не поддерживается!\n");

return -1; // кодек не найден

}

// Открываем кодек

if(avcodec_open(pCodecCtx, pCodec)<0)

return -1; // Не удалось открыть кодек

Некоторые из вас, возможно, помнят из старого учебника, что существуют две другие части кода: добавить CODEC_FLAG_TRUNCATED pCodecCtx->flags и хак, для исправиления неправильной частоты кадров. Эти два решения отсутствуют в ffplay.c, так что я считаю, что они больше не нужны. Еще отмечу что был удалён следующий код: pCodecCtx-> time_base, который вычисляет частоту кадров. Time_base это структура, содержащая числитель и знаменателе (AVRational ). Мы представляем частоту кадров в виде дроби, так как многие кодеки имеют нецелую частотоу кадров (например, в NTSC 29.97fps).

Запись данных

Теперь нам нужно реальное место для хранения кадра:

AVFrame *pFrame;

// Выделяем кадр

pFrame=avcodec_alloc_frame();

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

// Выделяем память для AVFrame

pFrameRGB=avcodec_alloc_frame();

if(pFrameRGB==NULL)

return -1;

Нам все еще нужно место для размещения полученных данных для конвертирования. Мы используем avpicture_get_size для получения размеров и выделения места вручную:

uint8_t *buffer;

int numBytes;

// Определить необходимый размер буфера и выделить буфер

numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,

pCodecCtx->height);

buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));

av_malloc — функция выделения памяти библиотеки FFmpeg — просто обертка системной функции malloc, гарантирующая то, что адреса памяти будут выравнены. Это не защитит вас от утечки памяти, двойного освобождения или других проблем.

Теперь мы используем avpicture_fill для связи кадра с недавно выделенным буфером. Структуры AVPicture является расширением AVFrame — начало AVFrame идентично AVPicture.

// Присвоить соответствующие части буфера кадров pFrameRGB

// Обратите внимание, что pFrameRGB имеет тип AVFrame, т.к. AVFrame является

// расширением AVPicture

avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,

pCodecCtx->width, pCodecCtx->height);

Наконец-то! Теперь мы готовы читать из потока!

Чтение данных

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

int frameFinished;

AVPacket packet;

i=0;

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

// Это пакет из видео потока?

if(packet.stream_index==videoStream) {

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

avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,

packet.data, packet.size);

//

// мы получили кадр?

if(frameFinished) {

// Преобразование изображения в формат RGB

img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24,

(AVPicture*)pFrame, pCodecCtx->pix_fmt,

pCodecCtx->width, pCodecCtx->height);

pCodecCtx->width, pCodecCtx->height);

Pages: ← previous Ctrl next
1 2

© Copyright (c) 2003 Fabrice Bellard, and a tutorial by Martin Bohme.

Original (English): An ffmpeg and SDL Tutorial Tutorial 01: Making Screencaps

Translation: © VlKhomenko .

License: Creative Commons Attribution-Share Alike 2.5

translated.by crowd

Like this translation? Share it or bookmark!