Учебник по ffmpeg и SDL Урок1 Создание стопкадров |
- Statistics
- Participants
- Translate into Russian
- Translation result
- Translation complete.
Обзор
Видеофайлы состоят из нескольки основных компонентов. Во-первых, сам файл называется контейнером, а тип контейнера определяет размещение информации в файле. Примеры контейнеров: 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);
© 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
