avcodec_encode_video2 时出现分段错误

use*_*087 5 c++ libav

尝试将 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);
}
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?

在调试时,我没有发现参数有任何问题。streamOut->codec充满。pktOut被分配并frame用之前编码的图片填充。我认为问题出在创建输出编解码器但观看示例并查看 doxypages 时,它似乎是正确的。

测试

跟踪路由来自使用 msvc11 和框架 5 的 QT。

我也试着和博士一起跑步。记忆并得到这个:

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
Run Code Online (Sandbox Code Playgroud)

看起来 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
Run Code Online (Sandbox Code Playgroud)

附录:

函数SafeFrame复制自教程 1

use*_*087 5

最后我解决了我的问题。

问题是(除了 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);
Run Code Online (Sandbox Code Playgroud)

该缓冲区用于从输入到输出的转换。然后在循环中,我必须用缓冲区填充输出图像(avframe)。我在代码中发现这个函数正在用缓冲区填充平面指针。 看这里

avpicture_fill((AVPicture*)outpic, outbuffer, AV_PIX_FMT_YUV420P, codecCtxOut->width, codecCtxOut->height);
Run Code Online (Sandbox Code Playgroud)

然后我使用 .inpic 将 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);
Run Code Online (Sandbox Code Playgroud)

然后您可以将输出图像编码为 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);
Run Code Online (Sandbox Code Playgroud)

所以现在它对我有用(几乎没问题)。仍然是一个小的内存泄漏,但我可以稍后发现。