Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

Using libavformat and libavcodec

Martin Boehme (boehme@inb.uni-luebeckREMOVETHIS.de)

February 18, 2004

Библиотеки libavformat и libavcodec входят в состав ffmpeg и позволяют получить доступ к большинству видео-форматов. К сожалению , документации по эти библиотекам очень мало , как и примеров их использования.

При использовании библиотек libavformat/libavcodec часто приходится прибегать к методу тыка . Вот пример небольшой программы demo program , который работает с libavformat/libavcodec версии 0.4.8 .

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

Многие форматы (AVI например) не конкретизируют конкретный кодек , они устанавливают правила упаковки видео и аудио-потоков , вот почему например при открытии таких файлов у вас может не показываться картинка , поскольку на вашей системе отсутствует нужный кодек. Библиотека libavformat делает первичную работу и парсит контейнеры и разделяет потоки в контейнере , затем libavcodec уже непосредственно декодирует сами audio и video потоки.

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

Давайте глянем , как открыть видео-файл и получить доступ к потокам - streams . Инициализация libavformat/libavcodec:

av_register_all();
 

Регистрируются все доступные файловые форматы и кодеки . Вызов av_register_all() делается 1 раз . В принципе , можно открыть не все , а какой-то один формат.

Открываем файл :

AVFormatContext *pFormatCtx;
 const char      *filename="myvideo.mpg";
 
 // Open video file
 if(av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL)!=0)
     handle_error(); // Couldn't open file
 

Последние 3 параметра задают : file format, buffer size , format parameters. Мы устанавливаем их в NULL , тем самым заставляя libavformat автоматически определить формат файла.

Теперь получаем информацию о потоках - streams :

// Retrieve stream information
 if(av_find_stream_info(pFormatCtx)<0)
     handle_error(); // Couldn't find stream information
 

Заполняется поле streams в структуре AVFormatContext.

В этом примере мы будем работать только с видео-потоком. Прочитаем первый видео-поток , который найден в контейнере :

int            i, videoStream;
 AVCodecContext *pCodecCtx;
 
 // Find the first video stream
 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)
     handle_error(); // Didn't find a video stream
 
 // Get a pointer to the codec context for the video stream
 pCodecCtx=&pFormatCtx->streams[videoStream]->codec;
 

Идентифицируем кодек :

AVCodec *pCodec;
 
 // Find the decoder for the video stream
 pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
 if(pCodec==NULL)
     handle_error(); // Codec not found
 
 // Inform the codec that we can handle truncated bitstreams -- i.e.,
 // bitstreams where frame boundaries can fall in the middle of packets
 if(pCodec->capabilities & CODEC_CAP_TRUNCATED)
     pCodecCtx->flags|=CODEC_FLAG_TRUNCATED;
 
 // Open codec
 if(avcodec_open(pCodecCtx, pCodec)<0)
     handle_error(); // Could not open codec
 

Зачем нужен т.н. "truncated bitstreams" ? Данные в видео-потоке разбиты на пакеты. Поскольку количество данных для видео-фрейма - величина не постоянная , граница между двумя фреймами может не совпадать с границей пакетов . Поэтому мы обрабатываем эту ситуацию и сообщаем об этом кодеку .

frame rate - одна из наиболее важных информаций , которая хранится в структуре AVCodecContext. frame rates (типа NTSC's 29.97 fps) может быть с дробной частью , он хранится по частям в pCodecCtx->frame_rate плюс в pCodecCtx->frame_rate_base. Некоторые кодеки (напр. ASF) некорректно инициализируют frame rate , и мы фиксируем это :

// Hack to correct wrong frame rates that seem to be generated by some 
 // codecs
 if(pCodecCtx->frame_rate>1000 && pCodecCtx->frame_rate_base==1)
     pCodecCtx->frame_rate_base=1000;
 

Выделяем память для фреймов :

AVFrame *pFrame;
 
 pFrame=avcodec_alloc_frame();
 

Теперь можно приступить к декодированию видео .

Декодирование Video Frames

Контейнер может включать несколько аудио и видео потоков , каждый из которых разбит на пакеты определенного размера. Нужно прочитать эти пакеты с помощью libavformat, выбрать из них нужные нам и передать для декодирования с помощью libavcodec.

Напишем процедуру , которая будет возвращать очередной фрейм :

bool GetNextFrame(AVFormatContext *pFormatCtx, AVCodecContext *pCodecCtx, 
     int videoStream, AVFrame *pFrame)
 {
     static AVPacket packet;
     static int      bytesRemaining=0;
     static uint8_t  *rawData;
     static bool     fFirstTime=true;
     int             bytesDecoded;
     int             frameFinished;
 
     // First time we're called, set packet.data to NULL to indicate it
     // doesn't have to be freed
     if(fFirstTime)
     {
         fFirstTime=false;
         packet.data=NULL;
     }
 
     // Decode packets until we have decoded a complete frame
     while(true)
     {
         // Work on the current packet until we have decoded all of it
         while(bytesRemaining > 0)
         {
             // Decode the next chunk of data
             bytesDecoded=avcodec_decode_video(pCodecCtx, pFrame,
                 &frameFinished, rawData, bytesRemaining);
 
             // Was there an error?
             if(bytesDecoded < 0)
             {
                 fprintf(stderr, "Error while decoding frame\n");
                 return false;
             }
 
             bytesRemaining-=bytesDecoded;
             rawData+=bytesDecoded;
 
             // Did we finish the current frame? Then we can return
             if(frameFinished)
                 return true;
         }
 
         // Read the next packet, skipping all packets that aren't for this
         // stream
         do
         {
             // Free old packet
             if(packet.data!=NULL)
                 av_free_packet(&packet);
 
             // Read new packet
             if(av_read_packet(pFormatCtx, &packet)<0)
                 goto loop_exit;
         } while(packet.stream_index!=videoStream);
 
         bytesRemaining=packet.size;
         rawData=packet.data;
     }
 
 loop_exit:
 
     // Decode the rest of the last frame
     bytesDecoded=avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, 
         rawData, bytesRemaining);
 
     // Free last packet
     if(packet.data!=NULL)
         av_free_packet(&packet);
 
     return frameFinished!=0;
 }
 

теперь нужно просто в цикле вызывать GetNextFrame(). Многие кодеки возвращают картинку в формате YUV 420 (одной яркостью и 2 каналами цветов ), который можно сконвертироватьв в RGB. В libavcodec есть процедура img_convert, которая делает такую конверсию :

while(GetNextFrame(pFormatCtx, pCodecCtx, videoStream, pFrame))
 {
     img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24, (AVPicture*)pFrame, 
         pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
 
     // Process the video frame (save to disk etc.)
     DoSomethingWithTheImage(pFrameRGB);
 }
 

Картинка в формате RGB pFrameRGB :

AVFrame *pFrameRGB;
 int     numBytes;
 uint8_t *buffer;
 
 // Allocate an AVFrame structure
 pFrameRGB=avcodec_alloc_frame();
 if(pFrameRGB==NULL)
     handle_error();
 
 // Determine required buffer size and allocate buffer
 numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
     pCodecCtx->height);
 buffer=new uint8_t[numBytes];
 
 // Assign appropriate parts of buffer to image planes in pFrameRGB
 avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
     pCodecCtx->width, pCodecCtx->height);
 
Итак , мы прочитали и извлекли видео :
// Free the RGB image
 delete [] buffer;
 av_free(pFrameRGB);
 
 // Free the YUV frame
 av_free(pFrame);
 
 // Close the codec
 avcodec_close(pCodecCtx);
 
 // Close the video file
 av_close_input_file(pFormatCtx);
 
Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье
achuglazov@yandex.ru
  Здравствуйте.
Я пытался разобраться в ffmpeg. 
Пытался откомпилить ваш пример в Microsoft Visual C++ 2005 , 
а он мне выдаёт ошибку "error LNK2019: unresolved external symbol _avcodec_decode_video referenced in function _main	Work_2.obj". 
Я почитал на форуме про эту ошибку, там говорится что методы не прописаны в .h файлах. 

Так вот у меня вопрос как всё таки запустить ваш пример, точнее как правильно подключить библиотеки.
Спасибо.	

2010-05-25 18:08:42
Яковлев Сергей
  Если честно, то я не пытался это собирать в винде и смутно представляю, как это можно сделать.
Скорее всего, в солюшине виндового проекта нужно добавить ссыллку либо на библиотеку,
либо dll-ку, которая идет вместе с исполняемым файлом.
2010-05-25 19:26:24
achuglazov@yandex.ru
  Ладно я попробую. Спасибо.
2010-05-28 18:34:47
achuglazov@yandex.ru
  поставил Mac OS 
 ну и вобщем:

Alexs-Mac:Tuttorial№1 alex$ gcc -o tutorial01 tutorial01.c
Undefined symbols:
  "_avcodec_find_decoder", referenced from:
      _main in ccFjcPkk.o
  "_avcodec_alloc_frame", referenced from:
      _main in ccFjcPkk.o
      _main in ccFjcPkk.o
  "_avpicture_get_size", referenced from:
      _main in ccFjcPkk.o
  "_av_close_input_file", referenced from:
      _main in ccFjcPkk.o
  "_av_malloc", referenced from:
      _main in ccFjcPkk.o
  "_av_read_frame", referenced from:
      _main in ccFjcPkk.o
  "_av_register_all", referenced from:
      _main in ccFjcPkk.o
  "_av_find_stream_info", referenced from:
      _main in ccFjcPkk.o
  "_avpicture_fill", referenced from:
      _main in ccFjcPkk.o
  "_av_free", referenced from:
      _main in ccFjcPkk.o
      _main in ccFjcPkk.o
      _main in ccFjcPkk.o
  "_av_open_input_file", referenced from:
      _main in ccFjcPkk.o
  "_avcodec_open", referenced from:
      _main in ccFjcPkk.o
  "_avcodec_decode_video", referenced from:
      _main in ccFjcPkk.o
  "_avcodec_close", referenced from:
      _main in ccFjcPkk.o
  "_img_convert", referenced from:
      _main in ccFjcPkk.o
  "_dump_format", referenced from:
      _main in ccFjcPkk.o
ld: symbol(s) not found
collect2: ld returned 1 exit status
Alexs-Mac:Tuttorial№1 alex$ 


Как можно с этим побороться?)
2010-05-28 19:38:53
Яковлев Сергей
  >> Alexs-Mac:Tuttorial№1 alex$ gcc -o tutorial01 tutorial01.c

А либы кто будет подключать ?

Я не знаю, как там на маке, но по иднее нужно так:

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





2010-05-28 19:58:22
achuglazov@yandex.ru
  Я запустил так как вы сказали...
library not found for -lavutil
 Вот  а гугл зараза ничего не выдает по этой ошибке...

2010-05-31 13:01:36
Яковлев Сергей
  А библиотека lavutil тут не нужна.
Для примера, который описан на этой странице, правильная команда:
  g++ -o avcodec_sample avcodec_sample.cpp -lavformat -lavcodec -lz
2010-05-31 13:48:21
achuglazov@yandex.ru
  Во вот так:
gcc -L"UsersDimaDesktopLibraryffmpeg"libavdevice 
-L"UsersDimaDesktopLibraryffmpeg"libavformat 
-L"UsersDimaDesktopLibraryffmpeg"libavcodec 
-L"UsersDimaDesktopLibraryffmpeg"libavutil -Wl,-dynamic,-search_paths_first  -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lbz2  -lavutil -lz

а параметры которые передаются через -L типа пути к этип библиотекам...вот)
теперь работает, и можно дальше изучать...)) 
2010-05-31 16:31:15
achuglazov@yandex.ru
  Снова привет. 
А как считать картинку, например JPEG в фрейм или пакет?
вот...а то как достать картинку из видео понятно, а как создать своё видео из картинок пока неочень...
2010-06-03 14:28:28
Яковлев Сергей
  Как-то так:
ffmpeg -r 10 -b 1800 -i *.jpg -y test.avi
2010-06-04 23:36:37
user
  По ссылке с демо-программой открывается неформатированный листинг - читать тяжело.
2016-04-28 00:40:41
Яковлев Сергей
  Поправил
2016-04-28 09:00:17