[Перевод] [Часть 1/2] Руководство по FFmpeg и SDL или Как написать видеоплеер менее чем в 1000 строк

8glm9bxivqkvscmah31ojdjmzci.png


Хотя данная информация уже устарела, оригинальный материал и по сей день является популярным источником вдохновения для различного полезнейшего контента по теме FFmpeg. При этом полного перевода оригинала на русский язык до сих пор нет. Исправляем досадное упущение, ибо лучше поздно, чем никогда.

И хотя мы старались, в таком объёмном тексте неизбежны трудности перевода. Сообщайте о недочётах (желательно, в личных сообщениях) — вместе сделаем лучше.

Оглавление

EDISON Software - web-development
Статья переведена при поддержке компании EDISON.

В нашем портфолио представлено в том числе и создание программного обеспечения для обработки видео, при этом часто пишем программы для работы с видео на C и C++.

Мы очень любим работать с видео! ;-)


Предисловие ↑


UPD: Данное руководство обновлено по состоянию на февраль 2015 года.

FFmpeg — отличная библиотека для создания видеоприложений, а также утилит общего назначения. FFmpeg берёт на себя всю рутину по обработке видео, выполняя всё декодирование, кодирование, мультиплексирование и демультиплексирование. Что заметно упрощает создание медиа-приложений. Всё достаточно простенько-быстренько, написано на C, можно декодировать практически любой кодек, имеющихся на сегодняшний день, а также кодировать в некоторые другие форматы.

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

Есть программа FFplay, поставляемая с FFmpeg. Она проста, написана на C, реализует полноценный видеоплеер с использованием FFmpeg. Первый мой урок — обновленная версии оригинального урока авторства Мартина Бёме (в оригинале тут ссылка на уже несуществующую веб-страницу — примечание переводчка) — я утащил кое-какие куски оттуда. А также в серии моих уроков покажу процесс создания работающего видеоплеера на основе ffplay.c Фабриса Белларда. В каждом уроке будет представлена новая идея (а то и две) с объяснением её реализации. К каждой главе прилагается листинг на C, который сможете самостоятельно скомпилировать и запустить. Исходные файлы покажут, как работает настоящая программа, как работают её отдельные части, а также продемонстрируют второстепенные технические детали, не затронутые в данном руководстве. Когда закончим, у нас будет рабочий видеоплеер, написанный менее чем в 1000 строк кода!

При создании плеера будем использовать SDL для вывода аудио и видео медиа-файла. SDL — это превосходная кроссплатформенная мультимедийная библиотека, используемая в программах воспроизведения MPEG, эмуляторах и многих видеоиграх. Вам необходимо будет загрузить и установить библиотеки для SDL в вашей системе, чтобы скомпилировать программы из этого руководства.

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

Пожалуйста, не стесняйтесь, присылайте мне сообщения об ошибках, вопросы, комментарии, идеи, функции, да что угодно, на Dranger собачка Gmail точка Com.

coi3m0tliby9r5uxtiydjoeaqkm.png

Читайте также в блоге
компании EDISON:


Руководство по FFmpeg libav

Урок 1: Создание скринкапсов ↑


Полный листинг: tutorial01.c
// tutorial01.c
// Code based on a tutorial by Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de)
// Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1
// With updates from https://github.com/chelyaev/ffmpeg-tutorial
// Updates tested on:
// LAVC 54.59.100, LAVF 54.29.104, LSWS 2.1.101 
// on GCC 4.7.2 in Debian February 2015

// A small sample program that shows how to use libavformat and libavcodec to
// read video from a file.
//
// Use
//
// gcc -o tutorial01 tutorial01.c -lavformat -lavcodec -lswscale -lz
//
// to build (assuming libavformat and libavcodec are correctly installed
// your system).
//
// Run using
//
// tutorial01 myvideofile.mpg
//
// to write the first five frames from "myvideofile.mpg" to disk in PPM
// format.

#include 
#include 
#include 

#include 

// compatibility with newer API
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif

void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
  FILE *pFile;
  char szFilename[32];
  int  y;
  
  // Open file
  sprintf(szFilename, "frame%d.ppm", iFrame);
  pFile=fopen(szFilename, "wb");
  if(pFile==NULL)
    return;
  
  // Write header
  fprintf(pFile, "P6\n%d %d\n255\n", width, height);
  
  // Write pixel data
  for(y=0; ydata[0]+y*pFrame->linesize[0], 1, width*3, pFile);
  
  // Close file
  fclose(pFile);
}

int main(int argc, char *argv[]) {
  // Initalizing these to NULL prevents segfaults!
  AVFormatContext   *pFormatCtx = NULL;
  int               i, videoStream;
  AVCodecContext    *pCodecCtxOrig = NULL;
  AVCodecContext    *pCodecCtx = NULL;
  AVCodec           *pCodec = NULL;
  AVFrame           *pFrame = NULL;
  AVFrame           *pFrameRGB = NULL;
  AVPacket          packet;
  int               frameFinished;
  int               numBytes;
  uint8_t           *buffer = NULL;
  struct SwsContext *sws_ctx = NULL;

  if(argc < 2) {
    printf("Please provide a movie file\n");
    return -1;
  }
  // Register all formats and codecs
  av_register_all();
  
  // Open video file
  if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0)
    return -1; // Couldn't open file
  
  // Retrieve stream information
  if(avformat_find_stream_info(pFormatCtx, NULL)<0)
    return -1; // Couldn't find stream information
  
  // Dump information about file onto standard error
  av_dump_format(pFormatCtx, 0, argv[1], 0);
  
  // Find the first video stream
  videoStream=-1;
  for(i=0; inb_streams; i++)
    if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
      videoStream=i;
      break;
    }
  if(videoStream==-1)
    return -1; // Didn't find a video stream
  
  // Get a pointer to the codec context for the video stream
  pCodecCtxOrig=pFormatCtx->streams[videoStream]->codec;
  // Find the decoder for the video stream
  pCodec=avcodec_find_decoder(pCodecCtxOrig->codec_id);
  if(pCodec==NULL) {
    fprintf(stderr, "Unsupported codec!\n");
    return -1; // Codec not found
  }
  // Copy context
  pCodecCtx = avcodec_alloc_context3(pCodec);
  if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
    fprintf(stderr, "Couldn't copy codec context");
    return -1; // Error copying codec context
  }

  // Open codec
  if(avcodec_open2(pCodecCtx, pCodec, NULL)<0)
    return -1; // Could not open codec
  
  // Allocate video frame
  pFrame=av_frame_alloc();
  
  // Allocate an AVFrame structure
  pFrameRGB=av_frame_alloc();
  if(pFrameRGB==NULL)
    return -1;

  // Determine required buffer size and allocate buffer
  numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
			      pCodecCtx->height);
  buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
  
  // Assign appropriate parts of buffer to image planes in pFrameRGB
  // Note that pFrameRGB is an AVFrame, but AVFrame is a superset
  // of AVPicture
  avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
		 pCodecCtx->width, pCodecCtx->height);
  
  // initialize SWS context for software scaling
  sws_ctx = sws_getContext(pCodecCtx->width,
			   pCodecCtx->height,
			   pCodecCtx->pix_fmt,
			   pCodecCtx->width,
			   pCodecCtx->height,
			   PIX_FMT_RGB24,
			   SWS_BILINEAR,
			   NULL,
			   NULL,
			   NULL
			   );

  // Read frames and save first five frames to disk
  i=0;
  while(av_read_frame(pFormatCtx, &packet)>=0) {
    // Is this a packet from the video stream?
    if(packet.stream_index==videoStream) {
      // Decode video frame
      avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
      
      // Did we get a video frame?
      if(frameFinished) {
	// Convert the image from its native format to RGB
	sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
		  pFrame->linesize, 0, pCodecCtx->height,
		  pFrameRGB->data, pFrameRGB->linesize);
	
	// Save the frame to disk
	if(++i<=5)
	  SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, 
		    i);
      }
    }
    
    // Free the packet that was allocated by av_read_frame
    av_free_packet(&packet);
  }
  
  // Free the RGB image
  av_free(buffer);
  av_frame_free(&pFrameRGB);
  
  // Free the YUV frame
  av_frame_free(&pFrame);
  
  // Close the codecs
  avcodec_close(pCodecCtx);
  avcodec_close(pCodecCtxOrig);

  // Close the video file
  avformat_close_input(&pFormatCtx);
  
  return 0;
}


Обзор


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

Работать с видео и аудио потоками очень просто даже на самом базовом уровне:

10 OPEN video_stream FROM video.avi
20 READ packet FROM video_stream INTO frame
30 IF frame NOT COMPLETE GOTO 20
40 DO SOMETHING WITH frame
50 GOTO 20


Работа с мультимедиа с помощью FFmpeg почти настолько же проста, как и в этой программе, хотя в некоторых программах шаг «СДЕЛАТЬ […]» может оказаться весьма сложным. В этом уроке мы откроем файл, считаем видеопоток внутри него, и наш «СДЕЛАТЬ […]» будет записывать кадр в файл PPM.

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


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

#include 
#include 
#include 
...
int main(int argc, charg *argv[]) {
av_register_all();


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

Теперь открываем файл:

AVFormatContext *pFormatCtx = NULL;

// Open video file
if(avformat_open_input(&pFormatCtx, argv[1], NULL, 0, NULL)!=0)
  return -1; // Couldn't open file


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

Эта функция смотрит только на заголовок, так что теперь нам нужно проверить информацию о потоке в файле:

// Retrieve stream information
if(avformat_find_stream_info(pFormatCtx, NULL)<0)
  return -1; // Couldn't find stream information


Эта функция передаёт в pFormatCtxstreams достоверные данные. Мы знакомимся с удобной функцией отладки, показывающей нам, что там внутри:

// Dump information about file onto standard error
av_dump_format(pFormatCtx, 0, argv[1], 0);


Теперь pFormatCtxstreams — это просто массив указателей размером pFormatCtxnb_streams. Пройдёмся по нему, пока не обнаружим видеопоток:

int i;
AVCodecContext *pCodecCtxOrig = NULL;
AVCodecContext *pCodecCtx = NULL;

// Find the first video stream
videoStream=-1;
for(i=0; inb_streams; i++)
  if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
    videoStream=i;
    break;
  }
if(videoStream==-1)
  return -1; // Didn't find a video stream

// Get a pointer to the codec context for the video stream
pCodecCtx=pFormatCtx->streams[videoStream]->codec;


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

AVCodec *pCodec = NULL;

// Find the decoder for the video stream
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==NULL) {
  fprintf(stderr, "Unsupported codec!\n");
  return -1; // Codec not found
}
// Copy context
pCodecCtx = avcodec_alloc_context3(pCodec);
if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
  fprintf(stderr, "Couldn't copy codec context");
  return -1; // Error copying codec context
}
// Open codec
if(avcodec_open2(pCodecCtx, pCodec)<0)
  return -1; // Could not open codec


Обратите внимание, что нельзя напрямую использовать AVCodecContext из видеопотока! Поэтому приходится использовать avcodec_copy_context(), чтобы скопировать контекст в новое место (разумеется, после того как для него выделена память).

Хранение данных


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

AVFrame *pFrame = NULL;

// Allocate video frame
pFrame=av_frame_alloc();


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

// Allocate an AVFrame structure
pFrameRGB=av_frame_alloc();
if(pFrameRGB==NULL)
  return -1;


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

uint8_t *buffer = NULL;
int numBytes;
// Determine required buffer size and allocate buffer
numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
                            pCodecCtx->height);
buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));


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

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

// Assign appropriate parts of buffer to image planes in pFrameRGB
// Note that pFrameRGB is an AVFrame, but AVFrame is a superset
// of AVPicture
avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
                pCodecCtx->width, pCodecCtx->height);


Мы уже на финишной прямой! Теперь мы готовы считывать с потока!

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


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

struct SwsContext *sws_ctx = NULL;
int frameFinished;
AVPacket packet;
// initialize SWS context for software scaling
sws_ctx = sws_getContext(pCodecCtx->width,
    pCodecCtx->height,
    pCodecCtx->pix_fmt,
    pCodecCtx->width,
    pCodecCtx->height,
    PIX_FMT_RGB24,
    SWS_BILINEAR,
    NULL,
    NULL,
    NULL
    );

i=0;
while(av_read_frame(pFormatCtx, &packet)>=0) {
  // Is this a packet from the video stream?
  if(packet.stream_index==videoStream) {
	// Decode video frame
    avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
    
    // Did we get a video frame?
    if(frameFinished) {
    // Convert the image from its native format to RGB
        sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
		  pFrame->linesize, 0, pCodecCtx->height,
		  pFrameRGB->data, pFrameRGB->linesize);
	
        // Save the frame to disk
        if(++i<=5)
          SaveFrame(pFrameRGB, pCodecCtx->width, 
                    pCodecCtx->height, i);
    }
  }
    
  // Free the packet that was allocated by av_read_frame
  av_free_packet(&packet);
}


Ничего сложного: av_read_frame() считывает пакет и сохраняет его в структуре AVPacket. Обратите внимание, что мы только распределяем структуру пакета — FFmpeg выделяет нам внутренние данные, на которые указывает packet.data. Это чуть позже освобождает av_free_packet(). avcodec_decode_video() преобразует пакет в кадр. Однако у нас может не быть всей информации, которая нам нужна для кадра после декодирования пакета, поэтому avcodec_decode_video() устанавливает frameFinished, когда у нас будет следующий кадр. Наконец, мы используем sws_scale() для преобразования из собственного формата (pCodecCtxpix_fmt) в RGB. Помните, что можно привести указатель AVFrame к указателю AVPicture. Наконец, передаём информацию о кадре, высоте и ширине нашей функции SaveFrame.

Теперь всё, что осталось сделать, это применить функцию SaveFrame для записи информации RGB в файл в формате PPM. Хотя мы поверхностно имеем дело с самим форматом PPM; поверьте, тут всё работает:

void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
  FILE *pFile;
  char szFilename[32];
  int  y;
  
  // Open file
  sprintf(szFilename, "frame%d.ppm", iFrame);
  pFile=fopen(szFilename, "wb");
  if(pFile==NULL)
    return;
  
  // Write header
  fprintf(pFile, "P6\n%d %d\n255\n", width, height);
  
  // Write pixel data
  for(y=0; ydata[0]+y*pFrame->linesize[0], 1, width*3, pFile);
  
  // Close file
  fclose(pFile);
}


Мы производим стандартное открытие файла и пр. А затем записываем данные RGB. Файл записываем построчно. Файл PPM — это просто файл, в котором информация RGB представлена в виде длинной строки. Если вы знаете цвета HTML, это будет похоже на разметку цвета каждого пикселя от первого конца до последнего, что-то вроде #ff0000#ff0000…, как для красного экрана. (На самом деле там хранится в двоичном формате и без разделителя, но, надеюсь, идею вы уловили.) В заголовке указано, насколько широким и высоким является изображение, а также максимальный размер значений RGB.

Теперь вернемся к нашей функции main(). Как только мы закончим чтение из видеопотока, нам просто нужно все очистить:

// Free the RGB image
av_free(buffer);
av_free(pFrameRGB);

// Free the YUV frame
av_free(pFrame);

// Close the codecs
avcodec_close(pCodecCtx);
avcodec_close(pCodecCtxOrig);

// Close the video file
avformat_close_input(&pFormatCtx);

return 0;


Как можете заметить, используем av_free для памяти, выделенной с помощью avcode_alloc_frame и av_malloc.

Вот и весь код! Теперь, если вы используете Linux или подобную платформу, то запускаете:

gcc -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lz -lavutil -lm


Если у вас более древняя версия FFmpeg, может потребоваться удалить -lavutil:

gcc -o tutorial01 tutorial01.c -lavformat -lavcodec -lz -lm


Большинство графических программ должны открывать формат PPM. Проверьте это на некоторых файлах фильма, скринкапсы которых сделаны с помощью нашей программы.

Урок 2: Вывод на экран ↑


Полный листинг: tutorial02.c
// tutorial02.c
// A pedagogical video player that will stream through every video frame as fast as it can.
//
// Code based on FFplay, Copyright (c) 2003 Fabrice Bellard, 
// and a tutorial by Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de)
// Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1
// With updates from https://github.com/chelyaev/ffmpeg-tutorial
// Updates tested on:
// LAVC 54.59.100, LAVF 54.29.104, LSWS 2.1.101, SDL 1.2.15
// on GCC 4.7.2 in Debian February 2015
//
// Use
// 
// gcc -o tutorial02 tutorial02.c -lavformat -lavcodec -lswscale -lz -lm `sdl-config --cflags --libs`
// to build (assuming libavformat and libavcodec are correctly installed, 
// and assuming you have sdl-config. Please refer to SDL docs for your installation.)
//
// Run using
// tutorial02 myvideofile.mpg
//
// to play the video stream on your screen.

#include 
#include 
#include 

#include 
#include 

#ifdef __MINGW32__
#undef main /* Prevents SDL from overriding main() */
#endif

#include 

// compatibility with newer API
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif

int main(int argc, char *argv[]) {
  AVFormatContext *pFormatCtx = NULL;
  int             i, videoStream;
  AVCodecContext  *pCodecCtxOrig = NULL;
  AVCodecContext  *pCodecCtx = NULL;
  AVCodec         *pCodec = NULL;
  AVFrame         *pFrame = NULL;
  AVPacket        packet;
  int             frameFinished;
  float           aspect_ratio;
  struct SwsContext *sws_ctx = NULL;

  SDL_Overlay     *bmp;
  SDL_Surface     *screen;
  SDL_Rect        rect;
  SDL_Event       event;

  if(argc < 2) {
    fprintf(stderr, "Usage: test \n");
    exit(1);
  }
  // Register all formats and codecs
  av_register_all();
  
  if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
    fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
    exit(1);
  }

  // Open video file
  if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0)
    return -1; // Couldn't open file
  
  // Retrieve stream information
  if(avformat_find_stream_info(pFormatCtx, NULL)<0)
    return -1; // Couldn't find stream information
  
  // Dump information about file onto standard error
  av_dump_format(pFormatCtx, 0, argv[1], 0);
  
  // Find the first video stream
  videoStream=-1;
  for(i=0; inb_streams; i++)
    if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
      videoStream=i;
      break;
    }
  if(videoStream==-1)
    return -1; // Didn't find a video stream
  
  // Get a pointer to the codec context for the video stream
  pCodecCtxOrig=pFormatCtx->streams[videoStream]->codec;
  // Find the decoder for the video stream
  pCodec=avcodec_find_decoder(pCodecCtxOrig->codec_id);
  if(pCodec==NULL) {
    fprintf(stderr, "Unsupported codec!\n");
    return -1; // Codec not found
  }

  // Copy context
  pCodecCtx = avcodec_alloc_context3(pCodec);
  if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
    fprintf(stderr, "Couldn't copy codec context");
    return -1; // Error copying codec context
  }

  // Open codec
  if(avcodec_open2(pCodecCtx, pCodec, NULL)<0)
    return -1; // Could not open codec
  
  // Allocate video frame
  pFrame=av_frame_alloc();

  // Make a screen to put our video
#ifndef __DARWIN__
        screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);
#else
        screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 24, 0);
#endif
  if(!screen) {
    fprintf(stderr, "SDL: could not set video mode - exiting\n");
    exit(1);
  }
  
  // Allocate a place to put our YUV image on that screen
  bmp = SDL_CreateYUVOverlay(pCodecCtx->width,
				 pCodecCtx->height,
				 SDL_YV12_OVERLAY,
				 screen);

  // initialize SWS context for software scaling
  sws_ctx = sws_getContext(pCodecCtx->width,
			   pCodecCtx->height,
			   pCodecCtx->pix_fmt,
			   pCodecCtx->width,
			   pCodecCtx->height,
			   PIX_FMT_YUV420P,
			   SWS_BILINEAR,
			   NULL,
			   NULL,
			   NULL
			   );



  // Read frames and save first five frames to disk
  i=0;
  while(av_read_frame(pFormatCtx, &packet)>=0) {
    // Is this a packet from the video stream?
    if(packet.stream_index==videoStream) {
      // Decode video frame
      avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
      
      // Did we get a video frame?
      if(frameFinished) {
	SDL_LockYUVOverlay(bmp);

	AVPicture pict;
	pict.data[0] = bmp->pixels[0];
	pict.data[1] = bmp->pixels[2];
	pict.data[2] = bmp->pixels[1];

	pict.linesize[0] = bmp->pitches[0];
	pict.linesize[1] = bmp->pitches[2];
	pict.linesize[2] = bmp->pitches[1];

	// Convert the image into YUV format that SDL uses
	sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
		  pFrame->linesize, 0, pCodecCtx->height,
		  pict.data, pict.linesize);

	SDL_UnlockYUVOverlay(bmp);
	
	rect.x = 0;
	rect.y = 0;
	rect.w = pCodecCtx->width;
	rect.h = pCodecCtx->height;
	SDL_DisplayYUVOverlay(bmp, &rect);
      
      }
    }
    
    // Free the packet that was allocated by av_read_frame
    av_free_packet(&packet);
    SDL_PollEvent(&event);
    switch(event.type) {
    case SDL_QUIT:
      SDL_Quit();
      exit(0);
      break;
    default:
      break;
    }

  }
  
  // Free the YUV frame
  av_frame_free(&pFrame);
  
  // Close the codec
  avcodec_close(pCodecCtx);
  avcodec_close(pCodecCtxOrig);
  
  // Close the video file
  avformat_close_input(&pFormatCtx);
  
  return 0;
}


SDL и видео


Для прорисовки на экране будем использовать SDL. SDL расшифровывается как Simple Direct Layer. Это преотличная кроссплатформенная библиотека для мультимедиа, используемая во многих проектах. Получить библиотеку можно на официальном сайте или загрузить пакет разработчика для своей операционной системы, если таковой есть. Понадобится библиотеки для компиляции кода из этого урока (всех остальных уроков это, кстати, тоже касается).

У SDL есть много методов для рисования на экране. Один из способов отображения фильмов — то, что называется наложением YUV.

Формально, даже не YUV, а YCbCr. У некоторых, между прочим, сильно подгорает, когда «YCbCr» называют как «YUV». Вообще говоря, YUV — это аналоговый формат, а YCbCr — это цифровой формат. FFmpeg и SDL в своём коде и в макросах обозначают YCbCr как YUV, но то такое.

YUV — способ хранения необработанных данных изображения, таких как RGB. Грубо говоря, Y является компонентом яркости, а U и V являются компонентами цвета. (Это более сложно, чем RGB, потому что часть информации о цвете отбрасывается, и можно иметь только 1 замер U и V на каждые 2 замера Y). Наложение YUV в SDL принимает необработанный массив данных YUV и отображает его. Он принимает 4 различных вида форматов YUV, но YV12 является из них самым быстрым. Существует другой формат YUV, называемый YUV420P, который совпадает с YV12, за исключением того, что массивы U и V меняются местами. 420 означает, что он подвергается дискретизации в соотношении 4:2:0, т.е., на каждые 4 замера яркости приходится 1 замер цвета, поэтому информация о цвете распределяется на четверти. Это хороший способ экономии пропускной способности, поскольку человеческий глаз всё равно не замечает этих изменений. Латинская буква «P» в названии говорит о том, что формат «плоскостной» (planar), это попросту означает, что компоненты Y, U и V находятся в отдельных массивах. FFmpeg может конвертировать изображения в YUV420P, что очень кстати, ибо многие видеопотоки уже хранятся в этом формате или легко конвертируются в него.

Таким образом, наш текущий план состоит в том, чтобы заменить функцию SaveFrame() из предыдущего урока и вместо этого вывести наш кадр на экран. Но сначала нужно ознакомиться с базовыми возможностями библиотеки SDL. Для начала подключаем библиотеки и инициализируем SDL:

#include 
#include 

if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
  fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
  exit(1);
}


SDL_Init(), по существу, сообщает библиотеке, какие функции будем использовать. SDL_GetError(), понятное дело, это наша удобная функция для отладки.

Создание дисплея


Теперь нам нужно место на экране, чтобы расположить элементы. Основная область для отображения изображений с SDL называется поверхностью:

SDL_Surface *screen;

screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);
if(!screen) {
  fprintf(stderr, "SDL: could not set video mode - exiting\n");
  exit(1);
}


Так мы устанавили экран с заданной шириной и высотой. Следующая опция — битовая глубина экрана — 0 — это специальное значение, которое означает «такой же, как и текущий дисплей».

Теперь мы создаем YUV-оверлей на этом экране, чтобы мы могли выводить на него видео, и настраиваем наш SWSContext для преобразования данных изображения в YUV420:

SDL_Overlay     *bmp = NULL;
struct SWSContext *sws_ctx = NULL;

bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height,
                           SDL_YV12_OVERLAY, screen);

// initialize SWS context for software scaling
sws_ctx = sws_getContext(pCodecCtx->width,
                         pCodecCtx->height,
			 pCodecCtx->pix_fmt,
			 pCodecCtx->width,
			 pCodecCtx->height,
			 PIX_FMT_YUV420P,
			 SWS_BILINEAR,
			 NULL,
			 NULL,
			 NULL
			 );


Как упоминали, используем YV12 для показа изображения и получаем данные YUV420 из FFmpeg.

Показ изображения


Ну, это было достаточно просто! Теперь нам просто нужно показать изображение. Давайте пройдем весь путь до того места, где у нас был готовый кадр. Мы можем избавиться от всего того, что у нас было для фрейма RGB, и мы собираемся заменить SaveFrame() нашим кодом отображения. Чтобы отобразить изображение, мы собираемся создать структуру AVPicture и установить для неё указатели данных и размер линии для нашего наложения YUV:

  if(frameFinished) {
    SDL_LockYUVOverlay(bmp);

    AVPicture pict;
    pict.data[0] = bmp->pixels[0];
    pict.data[1] = bmp->pixels[2];
    pict.data[2] = bmp->pixels[1];

    pict.linesize[0] = bmp->pitches[0];
    pict.linesize[1] = bmp->pitches[2];
    pict.linesize[2] = bmp->pitches[1];

    // Convert the image into YUV format that SDL uses
    sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
	      pFrame->linesize, 0, pCodecCtx->height,
	      pict.data, pict.linesize);
    
    SDL_UnlockYUVOverlay(bmp);


Поначалу мы блокируем оверлей, потому что мы планируем записывать в него. Это хорошая привычка, чтобы позже не возникало проблем. Структура AVPicture, как показано выше, имеет указатель данных, который представляет собой массив из 4-х указателей. Поскольку здесь мы имеем дело с YUV420P, у нас есть только 3 канала и, следовательно, только 3 набора данных. Другие форматы могут иметь четвертый указатель для альфа-канала или чего-то ещё. Размер линии — вот на что это похоже. Аналогичные структуры в нашем наложении YUV — это переменные для пикселей и высот. (Питчи, pitches — если изъясняться в терминах SDL для обозначения ширины заданной строки данных.) Итак, мы указываем три массива pict.data на нашем оверлее, поэтому, когда мы пишем в pict, мы на самом деле производим запись в наше наложение, которое, конечно, уже имеет необходимое пространство, выделенное специально для него. Точно так же мы получаем информацию о размерах линий непосредственно из нашего оверлея. Мы меняем формат преобразования на PIX_FMT_YUV420P и используем sws_scale, как и раньше.

Прорисовка изображения


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

SDL_Rect rect;

  if(frameFinished) {
    /* ... code ... */
    // Convert the image into YUV format that SDL uses
    sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
              pFrame->linesize, 0, pCodecCtx->height,
	      pict.data, pict.linesize);
    
    SDL_UnlockYUVOverlay(bmp);
	rect.x = 0;
	rect.y = 0;
	rect.w = pCodecCtx->width;
	rect.h = pCodecCtx->height;
	SDL_DisplayYUVOverlay(bmp, &rect);
  }


Вот теперь наше видео отображается!

Давайте покажем ещё одну особенность SDL: систему событий. SDL настроен таким образом, что при вводе или перемещении мыши в приложении SDL или отправке ему сигнала — генерируется событие. Затем ваша программа проверяет эти события, если подразумевается обработка вводимых пользователем данных. Ваша программа также может создавать события для отправки системе событий SDL. Это особенно полезно при многопоточном программировании с SDL, что мы увидим в уроке №4. В нашей программе мы собираемся проверять события сразу после завершения обработки пакета. На данный момент мы собираемся обработать событие SDL_QUIT, чтобы мы могли выйти:

SDL_Event       event;

    av_free_packet(&packet);
    SDL_PollEvent(&event);
    switch(event.type) {
    case SDL_QUIT:
      SDL_Quit();
      exit(0);
      break;
    default:
      break;
    }

И вот так и живём! Избавляемся от всего старого мусора и мы готовы к компиляции. Если используете Linux или что-то линуксоподобное, лучший способ компиляции с использованием библиотек SDL:

gcc -o tutorial02 tutorial02.c -lavformat -lavcodec -lswscale -lz -lm \
`sdl-config --cflags --libs`


sdl-config просто выводит нужные флаги для gcc, чтобы корректно включать библиотеки SDL. Возможно, придётся сделать что-то другое, чтобы заставить это скомпилироваться в вашей системе; пожалуйста, на всякий пожарный проверьте документацию SDL для вашей системы. Как только скомпилируется, продолжайте и запускайте.

Что происходит, когда вы запускаете эту программу? Видео как будто бы сходит с ума! Фактически, мы просто отображаем все видеокадры настолько быстро, насколько получается извлечь их из файла фильма. У нас сейчас нет кода, чтобы выяснить, когда нам нужно показывать видео. В конце концов (в уроке №5) мы приступим к синхронизации видео. Но на данный момент мы упускаем кое-что не менее важное: звук!

Урок 3: Воспроизведение звука ↑


Полный листинг: tutorial03.c
// tutorial03.c
// A pedagogical video player that will stream through every video frame as fast as it can
// and play audio (out of sync).
//
// Code based on FFplay, Copyright (c) 2003 Fabrice Bellard, 
// and a tutorial by Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de)
// Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1
// With updates from https://github.com/chelyaev/ffmpeg-tutorial
// Updates tested on:
// LAVC 54.59.100, LAVF 54.29.104, LSWS 2.1.101, SDL 1.2.15
// on GCC 4.7.2 in Debian February 2015
//
// Use
//
// gcc -o tutorial03 tutorial03.c -lavformat -lavcodec -lswscale -lz -lm `sdl-config --cflags --libs`
// to build (assuming libavformat and libavcodec are correctly installed, 
// and assuming you have sdl-config. Please refer to SDL docs for your installation.)
//
// Run using
// tutorial03 myvideofile.mpg
//
// to play the stream on your screen.

#include 
#include 
#include 

#include 
#include 

#ifdef __MINGW32__
#undef main /* Prevents SDL from overriding main() */
#endif

#include 
#include 

// compatibility with newer API
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif

#define SDL_AUDIO_BUFFER_SIZE 1024
#define MAX_AUDIO_FRAME_SIZE 192000

typedef struct PacketQueue {
  AVPacketList *first_pkt, *last_pkt;
  int nb_packets;
  int size;
  SDL_mutex *mutex;
  SDL_cond *cond;
} PacketQueue;

PacketQueue audioq;

int quit = 0;

void packet_queue_init(PacketQueue *q) {
  memset(q, 0, sizeof(PacketQueue));
  q->mutex = SDL_CreateMutex();
  q->cond = SDL_CreateCond();
}
int packet_queue_put(PacketQueue *q, AVPacket *pkt) {

  AVPacketList *pkt1;
  if(av_dup_packet(pkt) < 0) {
    return -1;
  }
  pkt1 = av_malloc(sizeof(AVPacketList));
  if (!pkt1)
    return -1;
  pkt1->pkt = *pkt;
  pkt1->next = NULL;
  
  
  SDL_LockMutex(q->mutex);
  
  if (!q->last_pkt)
    q->first_pkt = pkt1;
  else
    q->last_pkt->next = pkt1;
  q->last_pkt = pkt1;
  q->nb_packets++;
  q->size += pkt1->pkt.size;
  SDL_CondSignal(q->cond);
  
  SDL_UnlockMutex(q->mutex);
  return 0;
}
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
  AVPacketList *pkt1;
  int ret;
  
  SDL_LockMutex(q->mutex);
  
  for(;;) {
    
    if(quit) {
      ret = -1;
      break;
    }

    pkt1 = q->first_pkt;
    if (pkt1) {
      q->first_pkt = pkt1->next;
      if (!q->first_pkt)
	q->last_pkt = NULL;
      q->nb_packets--;
      q->size -= pkt1->pkt.size;
      *pkt = pkt1->pkt;
      av_free(pkt1);
      ret = 1;
      break;
    } else if (!block) {
      ret = 0;
      break;
    } else {
      SDL_CondWait(q->cond, q->mutex);
    }
  }
  SDL_UnlockMutex(q->mutex);
  return ret;
}

int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf, int buf_size) {

  static AVPacket pkt;
  static uint8_t *audio_pkt_data = NULL;
  static int audio_pkt_size = 0;
  static AVFrame frame;

  int len1, data_size = 0;

  for(;;) {
    while(audio_pkt_size > 0) {
      int got_frame = 0;
      len1 = avcodec_decode_audio4(aCodecCtx, &frame, &got_frame, &pkt);
      if(len1 < 0) {
	/* if error, skip frame */
	audio_pkt_size = 0;
	break;
      }
      audio_pkt_data += len1;
      audio_pkt_size -= len1;
      data_size = 0;
      if(got_frame) {
	data_size = av_samples_get_buffer_size(NULL, 
					       aCodecCtx->channels,
					       frame.nb_samples,
					       aCodecCtx->sample_fmt,
					       1);
	assert(data_size <= buf_size);
	memcpy(audio_buf, frame.data[0], data_size);
      }
      if(data_size <= 0) {
	/* No data yet, get more frames */
	continue;
      }
      /* We have data, return it and come back for more later */
      return data_size;
    }
    if(pkt.data)
      av_free_packet(&pkt);

    if(quit) {
      return -1;
    }

    if(packet_queue_get(&audioq, &pkt, 1) < 0) {
      return -1;
    }
    audio_pkt_data = pkt.data;
    audio_pkt_size = pkt.size;
  }
}

void audio_callback(void *userdata, Uint8 *stream, int len) {

  AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;
  int len1, audio_size;

  static uint8_t audio_buf[(MAX_AUDIO_FRAME_SIZE * 3) / 2];
  static unsigned int audio_buf_size = 0;
  static unsigned int audio_buf_index = 0;

  while(len > 0) {
    if(audio_buf_index >= audio_buf_size) {
      /* We have already sent all our data; get more */
      audio_size = audio_decode_frame(aCodecCtx, audio_buf, sizeof(audio_buf));
      if(audio_size < 0) {
	/* If error, output silence */
	audio_buf_size = 1024; // arbitrary?
	memset(audio_buf, 0, audio_buf_size);
      } else {
	audio_buf_size = audio_size;
      }
      audio_buf_index = 0;
    }
    len1 = audio_buf_size - audio_buf_index;
    if(len1 > len)
      len1 = len;
    memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
    len -= len1;
    stream += len1;
    audio_buf_index += len1;
  }
}

int main(int argc, char *argv[]) {
  AVFormatContext *pFormatCtx = NULL;
  int             i, videoStream, audioStream;
  AVCodecContext  *pCodecCtxOrig = NULL;
  AVCodecContext  *pCodecCtx = NULL;
  AVCodec         *pCodec = NULL;
  AVFrame         *pFrame = NULL;
  AVPacket        packet;
  int             frameFinished;
  struct SwsContext *sws_ctx = NULL;
  
  AVCodecContext  *aCodecCtxOrig = NULL;
  AVCodecContext  *aCodecCtx = NULL;
  AVCodec         *aCodec = NULL;

  SDL_Overlay     *bmp;
  SDL_Surface     *screen;
  SDL_Rect        rect;
  SDL_Event       event;
  SDL_AudioSpec   wanted_spec, spec;

  if(argc < 2) {
    fprintf(stderr, "Usage: test \n");
    exit(1);
  }
  // Register all formats and codecs
  av_register_all();
  
  if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
    fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
    exit(1);
  }

  // Open video file
  if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0)
    return -1; // Couldn't open file
  
  // Retrieve stream information
  if(avformat_find_stream_info(pFormatCtx, NULL)<0)
    return -1; // Couldn't find stream information
  
  // Dump information about file onto standard error
  av_dump_format(pFormatCtx, 0, argv[1], 0);
    
  // Find the first video stream
  videoStream=-1;
  audioStream=-1;
  for(i=0; inb_streams; i++) {
    if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO &&
       videoStream < 0) {
      videoStream=i;
    }
    if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO &&
       audioStream < 0) {
      audioStream=i;
    }
  }
  if(videoStream==-1)
    return -1; // Didn't find a video stream
  if(audioStream==-1)
    return -1;
   
  aCodecCtxOrig=pFormatCtx->streams[audioStream]->codec;
  aCodec = avcodec_find_decoder(aCodecCtxOrig->codec_id);
  if(!aCodec) {
    fprintf(stderr, "Unsupported codec!\n");
    return -1;
  }

  // Copy context
  aCodecCtx = avcodec_alloc_context3(aCodec);
  if(avcodec_copy_context(aCodecCtx, aCodecCtxOrig) != 0) {
    fprintf(stderr, "Couldn't copy codec context");
    return -1; // Error copying codec context
  }

  // Set audio settings from codec info
  wanted_spec.freq = aCodecCtx->sample_rate;
  wanted_spec.format = AUDIO_S16SYS;
  wanted_spec.channels = aCodecCtx->channels;
  wanted_spec.silence = 0;
  wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
  wanted_spec.callback = audio_callback;
  wanted_spec.userdata = aCodecCtx;
  
  if(SDL_OpenAudio(&wanted_spec, &spec) < 0) {
    fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
    return -1;
  }

  avcodec_open2(aCodecCtx, aCodec, NULL);

  // audio_st = pFormatCtx->streams[index]
  packet_queue_init(&audioq);
  SDL_PauseAudio(0);

  // Get a pointer to the codec context for the video stream
  pCodecCtxOrig=pFormatCtx->streams[videoStream]->codec;
  
  // Find the decoder for the video stream
  pCodec=avcodec_find_decoder(pCodecCtxOrig->codec_id);
  if(pCodec==NULL) {
    fprintf(stderr, "Unsupported codec!\n");
    return -1; // Codec not found
  }

  // Copy context
  pCodecCtx = avcodec_alloc_context3(pCodec);
  if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
    fprintf(stderr, "Couldn't copy codec context");
    return -1; // Error copying codec context
  }

  // Open codec
  if(avcodec_open2(pCodecCtx, pCodec, NULL)<0)
    return -1; // Could not open codec
  
  // Allocate video frame
  pFrame=av_frame_alloc();

  // Make a screen to put our video

#ifndef __DARWIN__
        screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);
#else
        screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 24, 0);
#endif
  if(!screen) {
    fprintf(stderr, "SDL: could not set video mode - exiting\n");
    exit(1);
  }
  
  // Allocate a place to put our YUV image on that screen
  bmp = SDL_CreateYUVOverlay(pCodecCtx->width,
				 pCodecCtx->height,
				 SDL_YV12_OVERLAY,
				 screen);

  // initialize SWS context for software scaling
  sws_ctx = sws_getContext(pCodecCtx->width,
			   pCodecCtx->height,
			   pCodecCtx->pix_fmt,
			   pCodecCtx->width,
			   pCodecCtx->height,
			   PIX_FMT_YUV420P,
			   SWS_BILINEAR,
			   NULL,
			   NULL,
			   NULL
			   );

  // Read frames and save first five frames to disk
  i=0;
  while(av_read_frame(pFormatCtx, &packet)>=0) {
    // Is this a packet from the video stream?
    if(packet.stream_index==videoStream) {
      // Decode video frame
      avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
      
      // Did we get a video frame?
      if(frameFinished) {
	SDL_LockYUVOverlay(bmp);

	AVPicture pict;
	pict.data[0] = bmp->pixels[0];
	pict.data[1] = bmp->pixels[2];
	pict.data[2] = bmp->pixels[1];

	pict.linesize[0] = bmp->pitches[0];
	pict.linesize[1] = bmp->pitches[2];
	pict.linesize[2] = bmp->pitches[1];

	// Convert the image into YUV format that SDL uses	
	sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
		  pFrame->linesize, 0, pCodecCtx->height,
		  pict.data, pict.linesize);
	
	SDL_UnlockYUVOverlay(bmp);
	
	rect.x = 0;
	rect.y = 0;
	rect.w = pCodecCtx->width;
	rect.h = pCodecCtx->height;
	SDL_DisplayYUVOverlay(bmp, &rect);
	av_free_packet(&packet);
      }
    } else if(packet.stream_index==audioStream) {
      packet_queue_put(&audioq, &packet);
    } else {
      av_free_packet(&packet);
    }
    // Free the packet that was allocated by av_read_frame
    SDL_PollEvent(&event);
    switch(event.type) {
    case SDL_QUIT:
      quit = 1;
      SDL_Quit();
      exit(0);
      break;
    default:
      break;
    }

  }

  // Free the YUV frame
  av_frame_free(&pFrame);
  
  // Close the codecs
  avcodec_close(pCodecCtxOrig);
  avcodec_close(pCodecCtx);
  avcodec_close(aCodecCtxOrig);
  avcodec_close(aCodecCtx);
  
  // Close the video file
  avformat_close_input(&pFormatCtx);
  
  return 0;
}


Аудио


Теперь нам хотелось бы, чтоб в приложении проигрывал звук. SDL также предоставляет нам методы для воспроизведения звука. Функция SDL_OpenAudio() используется для открытия самого аудиоустройства. Он принимает в качестве аргументов структуру SDL_AudioSpec, которая содержит всю информацию об аудио, которое мы собираемся воспроизвести.

Прежде чем покажем, как это настроить, сначала поясним, как вообще компьютер обрабатывает аудио. Цифровое аудио состоит из длинного потока сэмплов, каждый из которых представляет собой конкретное значение звуковой волны. Звуки записываются с определенной частотой дискретизации, которая просто говорит о том, как быстро воспроизводится каждый сэмпл, и измеряется количеством сэмплов в секунду. Примерные частоты дискретизации составляют 22 050 и 44 100 сэмплов в секунду, являющиеся скоростями, используемыми для радио и CD соответственно. Кроме того, большинство аудио может иметь более одного канала для стерео или об

© Habrahabr.ru