LINUX.ORG.RU

Простой захват видео при помощи libffmpeg

 


1

1

Вот «осенила» меня намедни мысль: а что, если вместо v4l2 попробовать использовать более «универсальный» способ захвата видео, который был бы независимым от типа фреймграббера или веб-камеры? Свой велосипед мне приходится допиливать каждый раз, как меняется источник видео (то он отдает в другом формате, то какие-то дополнительные ioctl'ы появляются, то вдруг используется веб-камера, которая гольный жопег отдает (а то и еще хуже).

Так вот: поверхностный «гуглеж» ничего путного не дал. Я не нашел примеров работы с libadevice, которые позволяли бы просто сделать что-то вроде

some_type *Dev = open_videodevice("/dev/videoX");
...
some_controls ctrl;
ctrl.brightness = 50;
ctrl.format = RGB;
adjust_controls(Dev, &ctrl);
...
unsigned char *frame = get_next_frame(Dev);

Как при помощи ffmpeg, не мучаясь с настройками v4l2, просто и быстро открыть устройство видеозахвата, настроить его и начать получать с него кадры?

// текущая реализация занимает несколько страниц вместо желаемого десятка строк



Последнее исправление: stave (всего исправлений: 1)

Ответ на: Решений нет? от Anon

libavdevice используется через тот же avformat_open_input, как и при работе с файлами. Примеры ищи по словам «av_find_input_format (video4linux2 | vfwcap)».

  f = av_find_input_format("video4linux2");
  av_dict_set(&opts, "video_size", "640x480", 0);
  av_dict_set(&opts, "pixel_format", "rgb", 0);
  av_open_input_file(&ctx, "/dev/video0", f, &opts);

Дальше, если камера отдает в другом формате, decode и scale, как обычно.

ffmpeg не все параметры v4l2 настраивает, например, яркость и контрастность. Их через v4l2 придется устанавливать. Или допилить ffmpeg, что даже проще.

fopen ★★
()
Последнее исправление: fopen (всего исправлений: 1)
Ответ на: комментарий от fopen

О, спасибо за наводку!

Буду искать. Просто что-то поначалу "гуглеж" никаких примеров не дал.

Anon
() автор топика
19 августа 2014 г.

На основе примера от Dranger'а получилось сделать элементарный захват:

// tutorial01.c
//
// This tutorial was written by Stephen Dranger (dranger@gmail.com).
//
// 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

// A small sample program that shows how to use libavformat and libavcodec to
// read video from a file.
//
// Use the Makefile to build all examples.
//
// Run using
//
// tutorial01 myvideofile.mpg
//
// to write the first five frames from "myvideofile.mpg" to disk in PPM
// format.

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

#include <stdio.h>

void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
  FILE *pFile;
  char szFilename[32];
  int  y;
printf("Frame saved\n");
  // 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; y<height; y++)
    fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);

  // Close file
  fclose(pFile);
}

int main(int argc, char *argv[]) {
  AVFormatContext *pFormatCtx = NULL;
  int             i, videoStream;
  AVCodecContext  *pCodecCtx = NULL;
  AVCodec         *pCodec = NULL;
  AVFrame         *pFrame = NULL;
  AVFrame         *pFrameRGB = NULL;
  AVPacket        packet;
  int             frameFinished;
  int             numBytes;
  uint8_t         *buffer = NULL;

AVInputFormat* ifmt;

  AVDictionary    *optionsDict = NULL;
  struct SwsContext      *sws_ctx = NULL;

  if(argc < 2) {
    printf("Please provide a device file name\n");
    return -1;
  }
av_log_set_level( AV_LOG_DEBUG );
  // Register all formats and codecs
  av_register_all();
avdevice_register_all();
ifmt = av_find_input_format("video4linux2");
if(!ifmt){printf("Can't find input format!\n"); return -1;}
  av_dict_set(&optionsDict, "analyzeduration", "0", 0);
  // Open video file
  if(avformat_open_input(&pFormatCtx, argv[1], ifmt, &optionsDict)!=0){
	  printf("Can't open device %s!\n", argv[1]);
    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; i<pFormatCtx->nb_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;

  // 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
  }
  // Open codec
  if(avcodec_open2(pCodecCtx, pCodec, &optionsDict)<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));
printf("Allocated buffer of %d bytes\n", numBytes);
  sws_ctx =
    sws_getContext
    (
        pCodecCtx->width,
        pCodecCtx->height,
        pCodecCtx->pix_fmt,
        pCodecCtx->width,
        pCodecCtx->height,
        PIX_FMT_RGB24,
        SWS_BILINEAR,
        NULL,
        NULL,
        NULL
    );

  // 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);

  // 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);
	else break;
      }
    }

    // Free the packet that was allocated by av_read_frame
    av_free_packet(&packet);
  }
  // Free the RGB image
  av_free(buffer);
  av_free(pFrameRGB);

  // Free the YUV frame
  av_free(pFrame);

  // Close the codec
  avcodec_close(pCodecCtx);

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

  return 0;
}

А и правда, удобней, нежели мучиться напрямую с v4l2. Буду использовать в дальнейшем.

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от i-rinat

Велосипед не мой. А sws_scale действительно идиотский формат данных имеет.

Зато эта библиотечка позволяет избавиться от тонны кода по перекодированию входного потока! У меня раньше жесть сколько страниц кода было на всякие эти операции. А сейчас ~250 строк подключаются к любому типу /dev/videoX, устанавливают нужный канал (при помощи v4l2 ioctl) и сохраняют захваченные 5 кадров.

Мне как раз пришлось вспомнить про свой допотопный велосипед. Я уже давно хотел его под libffmpeg переписать (т.к. каждый раз для нового устройства приходилось толпу кода дописывать: то кодирование другим было, то какие-то свойства не поддерживались)!

Сделаю демон, который будет "грабить" видео с указанного устройства, вычислять центр тяжести и складывать в двойной буфер в shm картинку + найденные координаты.

Другой демон будет считывать последние координаты и отправлять соответствующие команды на автогид.

А третий демон (CGI) будет отправлять кадры на веб-морду интерфейса управления прибором, либо на локальный openGL'ный интерфейс.

Надо будет еще с html5 что-нибудь придумать (т.к. потоковое считывание картинок в формате jpg требует периодической перезагрузки веб-страницы, иначе oom-killer убьет браузер, т.к. до сих пор браузеры не научились сразу же подчищать неиспользуемое).

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Eddy_Em

Не, ты не понял. sws_scale() требует массив из 4-х элементов, в которых лежат ссылки на плоскости. И даже если формат имеет одну плоскость, все 4 элемента могут внутри библиотеки использоваться. А ты просто подправляешь тип и вперёд.

А sws_scale действительно идиотский формат данных имеет.

Знаешь, лучшей похвалы я не видел. :-) Если что-то сделано грамотно, ты обязательно это обругаешь.

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Еще раз: велосипед не мой!!! Ты хоть копирайт в заголовке прочел бы!!!!!

И даже если формат имеет одну плоскость, все 4 элемента могут внутри библиотеки использоваться.

И что? Так работает? Работает! Ну и зачем что-то лишнее выдумывать?

Автор примера — не дурак!

лучшей похвалы я не видел

Угу, угу...

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Eddy_Em

И что? Так работает? Работает! Ну и зачем что-то лишнее выдумывать?

Это всё равно что крышу бумагой покрыть и говорить: «работает же! Зачем что-то лишнее придумывать?»

Автор примера — не дурак!

А никто и не спорит. Просто в коде ошибка.

Забавно на твои тирады смотреть. Вот ты поливаешь грязью авторов софта, которым пользуешься. А вот ты сам пишешь глючный софт, а на багрепорт отвечаешь «работает же!». Противоречия делают нашу жизнь веселее, ага.

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Просто в коде ошибка.

Почему тогда работает?

И как безошибочно сделать? Преобразовывать в "правильный формат"?

Ну, если надо будет — погуглю, что да как. Но пока работает, смысла это трогать нет. Лучше уделить внимание остальной части, которая обрабатывает полученные изображения.

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Eddy_Em

Почему тогда работает?

Неблагодарное это дело — рассуждать, почему работает некорректный код.

И как безошибочно сделать?

Завести пару массивов по 4 элемента каждый. В [0] одного класть ссылку на плоскость, в [0] второго — страйд. В sws_scale передавать ссылки на массивы.

http://sources.debian.net/src/chromium-browser/35.0.1916.153-2/src/third_part...

Но пока работает, смысла это трогать нет.

Вот у меня в соседней вкладке музон с youtube играет. И что характерно, стабильно так играет. Но вот где-то есть баг, из-за которого на одном китайском сайте, который делает что-то странное, флеш падает. И непонятно, где вообще ошибка, по коркам её отловить не получается. В такой ситуации я был бы рад, если бы кто-то показал на место в коде и сказал: «у тебя вот тут ошибка, так делать нельзя».

И предвосхищая ответ, скажу, что strdup() из glibc тоже падает, если ему NULL подсунуть или вообще левый указатель. Это не означает, что в strdup() ошибка.

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Да я уже нарыл примеры sws_scale на сайте ffmpeg'а.

Действительно, правильней создать массив из четырех указателей. Первый указатель и будут данные, остальные три — NULL.

Но пофиг же: в случае, приведенном выше, практически идентичное же поведение!

// но таки исправлю

strdup() из glibc тоже падает, если ему NULL подсунуть или вообще левый указатель

Прикольно. Не проверял. Похоже, в glibc отсутствует проверка аргумента на !NULL. Ну так ее несложно организовать в вызывающей strdup функции...

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Eddy_Em

Ну так ее несложно организовать в вызывающей strdup функции...

Ага, я так и сделал, nullsafe_strdup(). Только вот от адресов типа 0x123 это всё равно не защитит.

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Можно подумать, эти адреса из воздуха берутся! Ты ж обманываешь функцию, подсовывая липовые адреса!!!

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Eddy_Em

Эти адреса берутся из кода с ошибками. В моём случае что-то где-то портит память (но valgrind этого не замечает), из-за чего происходит разыменовывание некорректного указателя глубоко внутри Flash плеера. Код асинхронный, поэтому сложно понять, что случилось.

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Хе-хе:

uint8_t         *src[4], *dst[4];
...
        src[0] = pFrame->data;
        dst[0] = pFrameRGB->data;
        sws_scale
        (
            sws_ctx,
            //(uint8_t const * const *)pFrame->data,
            src,
            pFrame->linesize,
            0,
            pCodecCtx->height,
            dst,
            pFrameRGB->linesize
        );

не компиляется! Потому как, вполне логично, компилятор ругается:
capture.c: В функции <<main>>:
capture.c:221:13: ошибка: несовместимый тип указателя в присваивании [-Werror]
capture.c:222:13: ошибка: несовместимый тип указателя в присваивании [-Werror]
capture.c:233:9: ошибка: в передаче аргумента 2 <<sws_scale>>: несовместимый тип указателя [-Werror]
In file included from capture.c:30:0:
/usr/include/libswscale/swscale.h:226:5: замечание: expected <<const uint8_t * const*>> but argument is of type <<const uint8_t *>>
capture.c:233:9: ошибка: в передаче аргумента 6 <<sws_scale>>: несовместимый тип указателя [-Werror]
In file included from capture.c:30:0:
/usr/include/libswscale/swscale.h:226:5: замечание: expected <<uint8_t * const*>> but argument is of type <<const uint8_t *>>
cc1: all warnings being treated as errors
make: *** [obj/capture.o] Ошибка 1

Так что, все равно нужно преобразование типов. Только ХЗ какое: ни (uint8_t const * const *), ни (uint8_t * const *) не подходят. И, что интересно, в первом случае компилятор ругается так:

expected <<uint8_t * const*>> but argument is of type <<const uint8_t * const*>>
а во втором — так:
expected <<const uint8_t * const*>> but argument is of type <<uint8_t * const*>>
Издевается, скотина!!!

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Eddy_Em

А вот так:

	    src[0] = (uint8_t*)pFrame->data;
	    dst[0] = (uint8_t*)pFrameRGB->data;
        sws_scale
        (
            sws_ctx,
            //(uint8_t const * const *)pFrame->data,
            (const uint8_t *const *) src,
            pFrame->linesize,
            0,
            pCodecCtx->height,
            (uint8_t *const *) dst,
            pFrameRGB->linesize
        );
компилится, но не работает:
Assertion lumBufIndex < 2 * vLumBufSize failed at libswscale/swscale.c:512
Аварийный останов (core dumped)

Вот так-то!

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от i-rinat

В общем, ты меня с понту сбил!

Вот же:

int sws_scale 	( 	struct SwsContext *  	c,
		const uint8_t *const  	srcSlice[],
		const int  	srcStride[],
		int  	srcSliceY,
		int  	srcSliceH,
		uint8_t *const  	dst[],
		const int  	dstStride[] 
	) 	
Parameters
    c	the scaling context previously created with sws_getContext()
    srcSlice	the array containing the pointers to the planes of the source slice
    srcStride	the array containing the strides for each plane of the source image
    srcSliceY	the position in the source image of the slice to process, that is the number (counted starting from zero) in the image of the first row of the slice
    srcSliceH	the height of the source slice, that is the number of rows in the slice
    dst	the array containing the pointers to the planes of the destination image
    dstStride	the array containing the strides for each plane of the destination image 

и вот:

AVFrame Struct Reference
...
uint8_t * 	data [AV_NUM_DATA_POINTERS]
 	pointer to the picture/channel planes.
 
int 	linesize [AV_NUM_DATA_POINTERS]
 	For video, size in bytes of each picture line. 

Ничего преобразовывать в какие-то левые массивы не надо, т.к. AVFrame.data и есть тот самый массив srcSlice для входа sw_scale! Аналогично и с размером.

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Eddy_Em

Ничего преобразовывать в какие-то левые массивы не надо, т.к. AVFrame.data и есть тот самый массив srcSlice для входа sw_scale!

Ну извини, меня с толку сбило название поля «data» и явное преобразование типов. Подумал, что ты так сырые данные пихаешь в swscale.

i-rinat ★★★★★
()
Ответ на: комментарий от Eddy_Em

просто С типизирован чисто формально

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.