Ошибка сегментации при avcodec_encode_video2

У меня есть некоторые проблемы при попытке закодировать AVFrame в пакет.

Перед прочтением всего кода входные данные работают, я проверил это. Вывод вещи из примера Вот. Я думаю, что есть проблема. Но ошибка сегментации происходит в цикле ближе к концу.

Вот мой сокращенный код:

void nmain() {

// input stuff
AVFormatContext *formatCtxIn=0;
AVInputFormat *formatIn=0;
AVCodecContext *codecCtxIn=0;
AVCodec *codecIn;
AVPacket *pktIn;

av_register_all();
avdevice_register_all();
avcodec_register_all();formatIn = av_find_input_format("dshow");

if(!formatIn)
return;AVDictionary *avoption=0;
av_dict_set(&avoption, "rtbufsize", "1000000000", NULL);

if(avformat_open_input(&formatCtxIn, "video=Integrated Camera", formatIn, &avoption)!=0)
return;

if(avformat_find_stream_info(formatCtxIn, NULL)<0)
return;

codecCtxIn = formatCtxIn->streams[0]->codec;
codecIn = avcodec_find_decoder(codecCtxIn->codec_id);

if(avcodec_open2(codecCtxIn, codecIn, NULL)<0)
return;// end input stuff
//------------------------------------------------------------------------------
// output stuff

AVOutputFormat *formatOut=0;
AVFormatContext *formatCtxOut=0;
AVStream *streamOut=0;
AVFrame *frame=0;
AVCodec *codecOut=0;
AVPacket *pktOut;

const char *filename = "test.mpeg";

formatOut = av_guess_format(NULL, filename, NULL);
if(!formatOut)
formatOut = av_guess_format("mpeg", NULL, NULL);
if(!formatOut)
return;

formatCtxOut = avformat_alloc_context();
if(!formatCtxOut)
return;

formatCtxOut->oformat = formatOut;

sprintf(formatCtxOut->filename, "%s", filename);

if(formatOut->video_codec != AV_CODEC_ID_NONE) {
AVCodecContext *ctx;

codecOut = avcodec_find_encoder(formatOut->video_codec);
if(!codecOut)
return;

streamOut = avformat_new_stream(formatCtxOut, codecOut);
if(!streamOut)
return;

ctx = streamOut->codec;

ctx->bit_rate = 400000;
ctx->width    = 352;
ctx->height   = 288;
ctx->time_base.den = 25;
ctx->time_base.num = 1;
ctx->gop_size      = 12;
ctx->pix_fmt       = AV_PIX_FMT_YUV420P;

if(ctx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
ctx->max_b_frames = 2;
if(ctx->codec_id == AV_CODEC_ID_MPEG1VIDEO)
ctx->mb_decision = 2;if(formatCtxOut->oformat->flags & AVFMT_GLOBALHEADER)
ctx->flags |= CODEC_FLAG_GLOBAL_HEADER;
}

if(streamOut) {
AVCodecContext *ctx;
ctx = streamOut->codec;

if(avcodec_open2(ctx, codecOut, NULL) < 0)
return;
}

if(!(formatCtxOut->flags & AVFMT_NOFILE))
if(avio_open(&formatCtxOut->pb, filename, AVIO_FLAG_WRITE) < 0)
return;

avformat_write_header(formatCtxOut, NULL);// doit

pktIn = new AVPacket;
pktOut = new AVPacket;
av_init_packet(pktOut);
pktOut->data = 0;

frame = avcodec_alloc_frame();
if(!frame)
return;

for(;;) {
if(av_read_frame(formatCtxIn, pktIn) >= 0) {
av_dup_packet(pktIn);

int fff;
if(avcodec_decode_video2(codecCtxIn, frame, &fff, pktIn) < 0)
std::cout << "bad frame" << std::endl;

if(!fff)
return;  // ok

static int counter=0;
SaveFrame(frame, codecCtxIn->width, codecCtxIn->height, counter++);  // work fine

// here a segmentation fault is occured.
if(avcodec_encode_video2(streamOut->codec, pktOut, frame, &fff) < 0)
std::cout << "bad frame" << std::endl;
}
}
}// only for testing
// add to ensure frame is valid
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);
}

Что я делаю неправильно?

Во время отладки я не обнаружил никаких проблем с параметрами. streamOut->codec заполнено. pktOut выделяется и frame заполнен картиной, закодированной ранее.
Я думаю, что проблема заключается в создании выходного кодека, но при просмотре примера и просмотре страниц прокси это кажется правильным.

тестовое задание

Трасса трассировки от QT использует msvc11 и framework 5.

Я также пытался бежать с доктором. память и получить это:

Error #26: UNADDRESSABLE ACCESS: reading 0x00000000-0x00000004 4 byte(s)
# 0 replace_memcpy                      [d:\derek\drmemory\withwiki\trunk\drmemory\replace.c:203]
# 1 avcodec-54.dll!ff_dct_quantize_c   +0xd463   (0x6a482364 <avcodec-54.dll+0x3c2364>)
# 2 avcodec-54.dll!avcodec_encode_video2+0xb7     (0x6a56a5b8 <avcodec-54.dll+0x4aa5b8>)
# 3 nmain                               [d:\prg\tests\recording system-qt\libav\recsys\main.cpp:610]
# 4 main                                [d:\prg\tests\recording system-qt\libav\recsys\main.cpp:182]
Note: @0:00:06.318 in thread 5312
Note: instruction: mov    (%edx) -> %ebx

Кажется, что процесс чтения в то время как memcpy идет не так, как надо.


Версия:

Я забыл упомянуть версию libav / ffmpeg, которую я использую:

libavutil      51. 76.100 / 51. 76.100
libavcodec     54. 67.100 / 54. 67.100
libavformat    54. 33.100 / 54. 33.100
libavdevice    54.  3.100 / 54.  3.100
libavfilter     3. 19.103 /  3. 19.103
libswscale      2.  1.101 /  2.  1.101
libswresample   0. 16.100 /  0. 16.100
libpostproc    52.  1.100 / 52.  1.100

Приложение:

функция SafeFrame копируется из учебник 1.

4

Решение

Наконец я решил свою проблему.

Проблема в том, что (кроме документации libav) avpacket не является (реальной) копией картинки в пакете. это просто указывает на данные пакета. Вы должны сделать копию, или, что еще лучше, позволить сделать это libav.

Итак, сначала я создал новый avframe для вывода и буфер, на который указывает выходной avframe.

AVFrame *outpic = avcodec_alloc_frame();
nbytes = avpicture_get_size(codecCtxOut->pix_fmt, codecCtxOut->width, codecCtxOut->height);
uint8_t* outbuffer = (uint8_t*)av_malloc(nbytes);

Этот буфер используется для преобразования из ввода в вывод. Затем в цикле я должен заполнить outpic (avframe) буфером.
Я обнаружил в коде, что эта функция заполняет указатели плоскости буфером.
посмотреть здесь

avpicture_fill((AVPicture*)outpic, outbuffer, AV_PIX_FMT_YUV420P, codecCtxOut->width, codecCtxOut->height);

Затем я преобразовал Inpic в Outpic, используя sws_scale, Но сначала вы должны настроить swscontext.

SwsContext* swsCtx_ = sws_getContext(codecCtxIn->width, codecCtxIn->height,
codecCtxIn->pix_fmt,
codecCtxOut->width, codecCtxOut->height,
codecCtxOut->pix_fmt,
SWS_BICUBIC, NULL, NULL, NULL);

sws_scale(swsCtx_, inpic->data, inpic->linesize, 0, codecCtxIn->height, outpic->data, outpic->linesize);

Затем вы можете закодировать outpic в pktout (avpacket для вывода). Но сначала освободите выходной пакет, иначе вы получите ошибку и утечку … посмотреть здесь

av_free_packet(pktOut);

if(avcodec_encode_video2(streamOut->codec, pktOut, outpic, &fff) < 0) {
std::cout << "shit frame" << std::endl;
continue;
}
// and write it to the file
formatOut->write_packet(formatCtxOut, pktOut);

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

4

Другие решения

Я вижу по крайней мере две проблемы с этим циклом транскодирования:

1) Вы не проверяете, создал ли декодер кадр. У многих декодеров есть задержка между входом и выходом, поэтому вызов декодирования не обязательно создаст кадр, даже если не возникнет ошибка. Вам просто нужно продолжать передавать пакеты в декодер до тех пор, пока он не начнет возвращать кадры (а затем сбросить их с пустыми пакетами в конце, как описано в документация).

В результате вы передаете неинициализированный кадр кодеру, что, вероятно, является причиной сбоя.

2) Другая проблема, которую я вижу, состоит в том, что вы запускаете выходной пакет только один раз. Как документация говорит

Пользователь может предоставить выходной буфер, установив avpkt-> data и avpkt-> size до вызова функции, но если размер предоставленных пользователем данных недостаточно велик, кодирование завершится ошибкой. Все остальные поля AVPacket будут сброшены кодером с помощью av_init_packet (). Если avpkt-> data равен NULL, кодер выделит его. Кодировщик установит avpkt-> size размером выходного пакета. Возвращенные данные (если таковые имеются) принадлежат звонящему, он отвечает за их освобождение.

Так что, если вы инициализируете его только один раз перед началом цикла транскодирования, на каждой итерации после первой он будет содержать старые данные. Кодировщик подумает, что вы хотите использовать этот буфер для кодирования, и перезапишите его. Это закончится слезами, если вы уже передали этот пакет мультиплексору или что-то в этом роде. Поэтому убедитесь, что перед каждым вызовом кодирования данные пакета и его размер равны NULL / 0.

0

По вопросам рекламы [email protected]