5

我在尝试将 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填充了之前编码的图片。我认为问题在于创建输出编解码器但观看示例并查看 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

似乎 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

附录:

函数是从教程 1SafeFrame复制的。

4

2 回答 2

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

该缓冲区用于从输入到输出的转换。然后在循环中我必须用缓冲区填充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);

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

于 2013-06-11T22:41:16.310 回答
0

我看到该转码循环至少有两个问题:

1)您没有检查解码器是否产生了帧。许多解码器在输入和输出之间存在延迟,因此即使没有错误发生,解码调用也不一定会产生帧。您只需要继续将数据包传递给解码器,直到它开始返回帧(然后在最后用 NULL 数据包刷新它,如文档中所述)。

结果是您将未初始化的帧传递给编码器,这可能是崩溃的原因。

2)我看到的另一个问题是您只初始化输出数据包一次。正如文件所说

用户可以在调用函数之前通过设置 avpkt->data 和 avpkt->size 来提供一个输出缓冲区,但是如果用户提供的数据的大小不够大,编码将失败。编码器将使用 av_init_packet() 重置所有其他 AVPacket 字段。如果 avpkt->data 为 NULL,编码器将分配它。编码器会将 avpkt->size 设置为输出数据包的大小。返回的数据(如果有)属于调用者,他负责释放它。

因此,如果您只在开始转码循环之前对其进行一次初始化,那么在第一次之后的每次迭代中,它将包含旧数据。编码器会认为您想使用该缓冲区进行编码并覆盖它。如果您已经将该数据包传递给复用器或类似的东西,这将以眼泪告终。因此,请确保在每次编码调用之前将数据包数据和大小初始化为 NULL/0。

于 2013-06-07T19:58:04.147 回答