LINUX.ORG.RU

Си AVFrame + sws_scale + (видимо кривые руки) = segfault

 , , ,


1

1

Доброго времени суток, товарищи. Я столкнулся с проблемой, которую, видимо из-за отсутствия опыта и рук заточенных под си, не могу решить. Пишу программный видеорегистратор. На данном этапе состоит из заголовочного файла libav-dvr-monitor.h :

/* 
 * File:   libav-dvr-monitor.h
 * Author: ryvkin
 *
 * Created on 3 Сентябрь 2013 г., 23:11
 */

#ifndef LIBAV_DVR_MONITOR_H
#define	LIBAV_DVR_MONITOR_H

#ifdef	__cplusplus
extern "C" {
#endif
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libswscale/swscale.h>
#include <stdlib.h>
#include <stdbool.h>
	typedef struct DVRMonitorConnectionStruct{
		bool status;
		AVFormatContext * formatContext;
		int videoStreamOffset;
		struct AVCodecContext * codecContext;
		AVCodec * codec;
		struct SwsContext * swsContext;
		size_t bufferSize;
		u_int8_t * buffer;
	}DVRMonitorConnection;
	typedef struct DVRMonitorStruct{
		int id;
		char * dataSourceName;
		AVDictionary * options;
		DVRMonitorConnection * connection;
		AVFrame * lastFrame;
		AVFrame * lastRGBFrame;
	}DVRMonitor;
	
	DVRMonitor * dvrMonitorAllocte(){
		DVRMonitor * monitor=(DVRMonitor *) malloc(sizeof(DVRMonitor));
		monitor->id=0;
		monitor->dataSourceName=(char *) malloc(sizeof("rtsp://89.222.248.121/av0_1"));
		monitor->dataSourceName="rtsp://89.222.248.121/av0_0";
		printf("Trying to open datasource \"%s\" for monitor with id %i\n", monitor->dataSourceName, monitor->id);
		monitor->options=NULL;
		av_dict_set(&monitor->options, "rtsp_transport", "tcp", 0);
		monitor->connection=(DVRMonitorConnection *) malloc(sizeof(DVRMonitorConnection));
		monitor->connection->status=false;
		monitor->connection->videoStreamOffset=-1;
		monitor->connection->formatContext=NULL;
		monitor->connection->codecContext=NULL;
		monitor->connection->codec=NULL;
		monitor->connection->swsContext=NULL;
		monitor->connection->bufferSize=0;
		monitor->connection->buffer=NULL;
		return monitor;
	}
	bool dvrMonitorOpen(DVRMonitor * monitor){
		av_register_all();
		avformat_network_init();
		monitor->connection->formatContext=avformat_alloc_context();
		printf("Trying to open datasource \"%s\" for monitor with id %i\n", monitor->dataSourceName, monitor->id);
		if(avformat_open_input(&monitor->connection->formatContext, monitor->dataSourceName, NULL, &monitor->options)!=0){
			fprintf(stderr, "Error on monitor with id %i: can't open datasource \"%s\"\n", monitor->id, monitor->dataSourceName);
			return false;
		}
		if(avformat_find_stream_info(monitor->connection->formatContext, &monitor->options)<0){
			fprintf(stderr, "Error on monitor with id %i: can't find stream information for datasource \"%s\"\n", monitor->id, monitor->dataSourceName);
			avformat_close_input(&monitor->connection->formatContext);
		};
		int offset;
		monitor->connection->videoStreamOffset=-1;
		for(offset=0;offset<monitor->connection->formatContext->nb_streams;offset++){
			if(monitor->connection->formatContext->streams[offset]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
				monitor->connection->videoStreamOffset=offset;
				monitor->connection->codecContext=monitor->connection->formatContext->streams[offset]->codec;
				break;
			}
		}
		if(monitor->connection->videoStreamOffset<0){
			fprintf(stderr, "Error on monitor with id %i: video stream was not found in monitor datasource \"%s\"\n", monitor->id, monitor->dataSourceName);
			avformat_close_input(&monitor->connection->formatContext);
			return false;
		}
		monitor->connection->codec=avcodec_find_decoder(monitor->connection->codecContext->codec_id);
		if(monitor->connection->codec==NULL){
			fprintf(stderr, "Error on monitor with id %i: video stream (stream offset: %i) has unsupported codec \"%s\"\n", monitor->id, monitor->connection->videoStreamOffset, monitor->connection->codecContext->codec_name);
			return false;
		}
		if(avcodec_open2(monitor->connection->codecContext, monitor->connection->codec, &monitor->options)<0){
			fprintf(stderr, "Error on monitor with id %i: Can't open codec \"%s\"\n", monitor->id, monitor->connection->codecContext->codec_name);
			return false;
		}
		monitor->lastFrame=avcodec_alloc_frame();
		monitor->lastRGBFrame=avcodec_alloc_frame();
		monitor->connection->bufferSize=
			sizeof(u_int8_t)*
			avpicture_get_size(
				monitor->connection->codecContext->pix_fmt,
				monitor->connection->codecContext->width,
				monitor->connection->codecContext->height
			);
		monitor->connection->buffer=(u_int8_t *) av_malloc(monitor->connection->bufferSize);
		monitor->connection->swsContext=sws_getContext(
			monitor->connection->codecContext->width,
			monitor->connection->codecContext->height,
			monitor->connection->codecContext->pix_fmt,
			monitor->connection->codecContext->width,
			monitor->connection->codecContext->height,
			PIX_FMT_RGBA,
			SWS_BILINEAR,
			NULL,
			NULL,
			NULL
		);
		avpicture_fill(
			(AVPicture *) (monitor->lastRGBFrame),
			monitor->connection->buffer,
			PIX_FMT_RGBA,
			monitor->connection->codecContext->width,
			monitor->connection->codecContext->height
		);
		av_dump_format(monitor->connection->formatContext, 0, monitor->dataSourceName, 0);
		return true;
	}
	bool dvrMonitorReceiveFrame(DVRMonitor * monitor){
		AVPacket packet;
		int frameFinished=0;
		while(av_read_frame(monitor->connection->formatContext, &packet)>=0) {
			if(packet.stream_index==monitor->connection->videoStreamOffset) {
				avcodec_decode_video2(monitor->connection->codecContext, monitor->lastFrame, &frameFinished, &packet);
				if(frameFinished){
					printf("%i\n", frameFinished);
					sws_scale(
						monitor->connection->swsContext,
						(const u_int8_t * const*) monitor->lastFrame->data,
						monitor->lastFrame->linesize,
						0,
						monitor->connection->codecContext->height,
						monitor->lastRGBFrame->data,
						monitor->lastRGBFrame->linesize
					);
					return true;
				}
			}
			av_free_packet(&packet);
		}
		return false;
	}
	void dvrMonitorClose(DVRMonitor * monitor){
		
	}
#ifdef	__cplusplus
}
#endif

#endif	/* LIBAV_DVR_MONITOR_H */
и тестового main-файла main.c :
/* 
 * File:   main.c
 * Author: ryvkin
 *
 * Created on 29 Август 2013 г., 14:34
 */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <syslog.h>
#include "libav-dvr-monitor.h"
/*
 * Константы для задания цвета текста консоли
 */

#define TEXT_COLOR_NORMAL  "\x1B[0m"
#define TEXT_COLOR_RED  "\x1B[31m"
#define TEXT_COLOR_GREEN  "\x1B[32m"
#define TEXT_COLOR_YELOW  "\x1B[33m"
#define TEXT_COLOR_BLUE  "\x1B[34m"
#define TEXT_COLOR_MAGENTA  "\x1B[35m"
#define TEXT_COLOR_CYAN  "\x1B[36m"
#define TEXT_COLOR_WHITE  "\x1B[37m"
/*
 * pid_t daemonize() - функция демонизирует текущий процесс.
 */

pid_t daemonize(){
	pid_t process_id, sid;
	/* Clone ourselves to make a child */  
	process_id = fork(); 
	/* If the pid is less than zero, something went wrong when forking */
	if (process_id < 0) {
		exit(EXIT_FAILURE);
	}
	/* If the pid we got back was greater than zero, then the clone was successful and we are the parent. */
	if (process_id > 0) {
		exit(EXIT_SUCCESS);
	}
	umask(0);
	/* Try to create our own process group */
	sid = setsid();
	if (sid < 0) {
		//syslog(LOG_ERR, "Could not create process group\n");
		fprintf(stderr,  "%sCould not create process group%s\n", TEXT_COLOR_RED, TEXT_COLOR_NORMAL);
		exit(EXIT_FAILURE);
	}
	/* Change the current working directory */
	if ((chdir("/")) < 0) {
		//syslog(LOG_ERR, "Could not change working directory to /\n");
		fprintf(stderr,  "%sCould not change working directory to /%s\n", TEXT_COLOR_RED, TEXT_COLOR_NORMAL);
		exit(EXIT_FAILURE);
	}
	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);
	return getpid();
}

int main(int argc, char** argv) {
	//pid_t process_id=daemonize();

	DVRMonitor * monitor=dvrMonitorAllocte();
	if(dvrMonitorOpen(monitor)){
		printf("Connected\n");
		dvrMonitorReceiveFrame(monitor);
	}else{
		printf(":(\n");
	}
	return (EXIT_SUCCESS);
}
Код написан почти один в один как в tutorial'е (ввиду крайней необходимости объединил все данные используемые на каждый монитор в свои типы данных). Не взлетает :-(. gdb backtrace возвещает, что падает функция sws_scale(), не могу понять почему. Подскажите пожалуйста, что я делаю не так.

gdb backtrace возвещает, что падает функция sws_scale(), не могу понять почему

Поставь отладочные символы и посмотри, где именно падает.

Удостоверься, что все массивы, которые передаёшь в sws_scale имеют как минимум 4 элемента (даже если у тебя одна плоскость).

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

Падает в libswscale.so.2, где конкретнее gdb молчит.

Пересобери libswscale с включенной отладкой, замени ей ту, что в системе. Без этого можно только гадать, что не так.

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

А есть ли без пересборки (не так уж я хорошо знаю linux) возможность понять почему тут

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

  AVDictionary    *optionsDict = 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; 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=avcodec_alloc_frame();
  
  // Allocate an AVFrame structure
  pFrameRGB=avcodec_alloc_frame();
  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));

  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);
      }
    }
    
    // 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;
}
всё работает на ура, а у меня нет. Складывается ощущение, что я где-то с типами или указателями напутал.

ryvkin-sergey ()
Ответ на: комментарий от ryvkin-sergey

Например, разные форматы. PIX_FMT_RGB24 супротив PIX_FMT_RGBA. Ты вообще уверен, что у тебя swsContext не NULL?

А есть ли без пересборки

Проще уж посмотреть, где падает, и плясать оттуда, чем медитировать на простыни кода. Пробелов и там и тут пожалели, читать неудобно.

не так уж я хорошо знаю linux

Так знания и приходят.

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

я тут попробовал получить 4-ые элементы массивов

printf("%p %i\n", monitor->lastFrame->data[3], monitor->lastFrame->linesize[3]-1);
printf("%p %i\n", monitor->lastRGBFrame->data[3], monitor->lastRGBFrame->linesize[3]-1);
на выходе увидел:
(nil) -1
(nil) -1

ryvkin-sergey ()
Ответ на: комментарий от ryvkin-sergey

я тут попробовал получить 4-ые элементы массивов

Если в RGBA картинке одна плоскость, адекватные значения будут только в [0]. Там просто нужно, чтобы эти 4 элемента были, внутри есть обращение ко всем, вне зависимости от указанного формата. Я уже на этом спотыкался.

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

я тут заметил, что если в 4-й параметр передатьне 0, а скажем например 1, то sigsegv'а нет, однако либа ругается на

[swscaler @ 0x946a940] Slices start in the middle!

ryvkin-sergey ()
Ответ на: комментарий от ryvkin-sergey

желательно без make install а через менеджер пакетов (dpkg)

просто собери ffmpeg через ./configure && make, а потом руками скопируй получившуюся libswscale поверх дистрибутивной. При конфигурировании включи отладочную информацию. Это либо через --enable-debug, либо CFLAGS="-O0 -g3"

i-rinat ★★★★★ ()
Ответ на: комментарий от ryvkin-sergey
(gdb) backtrace
#0  0xb71bb695 in yuv2rgb_c_32 (c=0x810fd00, src=0xbfffef10, srcStride=0xbfffef30, srcSliceY=0, srcSliceH=720, dst=0xbfffef20, dstStride=0xbfffef40)
    at libswscale/yuv2rgb.c:217
#1  0xb71b3f36 in sws_scale (c=0x810fd00, srcSlice=0x805bc40, srcStride=0x805bc50, srcSliceY=0, srcSliceH=720, dst=0x805da00, dstStride=0x805da10)
    at libswscale/swscale_unscaled.c:1014
#2  0x080492dc in dvrMonitorReceiveFrame (monitor=0x804c028) at libav-dvr-monitor.h:135
#3  0x08049464 in main (argc=1, argv=0xbffff0b4) at main.c:78
ryvkin-sergey ()
Ответ на: комментарий от ryvkin-sergey

PUTRGB(dst_2,py_2,0);

Обычно в таких случаях проверяют переменные на NULL или мусор.

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

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

Я бы с удовольствием решил её сам, но к сожалению не знаю как. Так что буду крайне признателен, если будешь направлять куда и как смотреть, дабы найти решение. =)

ryvkin-sergey ()
Ответ на: комментарий от ryvkin-sergey

если будешь направлять куда и как смотреть

А я уже. Сказал же: «обычно в таких случаях проверяют переменные на NULL или мусор». В gdb есть команда print, ей можно смотреть содержимое переменных. Можно сократить до p. «p x» напечатает содержимое x, «p dst_2» — содержимое dst_2. Поддерживаются выражения на Си, указатели разыменовывать, всё такое.

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

Я вот полистал исходники yuv2rgb.c и плавно начинаю захлёбываться матом. Функции определяются макросами, и я в них уже потерялся. все эти dst приходят в макросы после неведомой цепи операций над ними, так что, даже когда я найду что пришёл мусор или null, судя по всему я ещё год буду разбираться откуда он там взялся =\.

ryvkin-sergey ()
Ответ на: комментарий от ryvkin-sergey

Функции определяются макросами

Тебе поможет gcc -E

судя по всему я ещё год буду разбираться откуда он там взялся

По стеку в gdb можно ходить через команду frame; точки останова ставятся командой b, с указанием имени файла и номером строки через двоеточие.

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

Я через ddd созерцаю и от bp до bp катаюсь на cont. Подскажи как макрос написать в gdb чтобы он автоматом выполнял 3 действия поочереди: p dst_2 p py_2 cont

ryvkin-sergey ()
Ответ на: комментарий от ryvkin-sergey

Подскажи как макрос написать в gdb чтобы он автоматом выполнял 3 действия поочереди: p dst_2 p py_2 cont

graph display dst_2
graph display py_2

Должны появиться в отдельной панели. Если просто «display dst_2», то будет в текстовом виде появляться в консоли gdb. В ddd можно по переменной в тексте жамкнуть правой кнопкой мыши и выбрать Display. Эффект тот же.

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat
Спасибо огромное за потраченое на меня время.
Я научился:
1)пересобирать пакеты (полностью или частично);
2)глубже познакомился с gdb и ddd;
3)понял, что лезть в чужие либы дело не благодарное.
А когда отчаялся, ещё раз сравнил свой код с примером и НАШЁЛ.
Вместо формата пикселей входящего фрейма в определении размера буфера
monitor->connection->bufferSize=
			sizeof(u_int8_t)*
			avpicture_get_size(
				monitor->connection->codecContext->pix_fmt,
				monitor->connection->codecContext->width,
				monitor->connection->codecContext->height
			);
нужно было указать формат RGB-фрейма
monitor->connection->bufferSize=
			sizeof(u_int8_t)*
			avpicture_get_size(
				PIX_FMT_RGBA,
				monitor->connection->codecContext->width,
				monitor->connection->codecContext->height
			);
ryvkin-sergey ()
Ответ на: комментарий от ryvkin-sergey

Круто. Ещё ознакомься с valgrind. Пригодится.

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