[Libav-user] mpeg4 decoding with CODEC_FLAG_TRUNCATED: memleak and missing last image

Gajdosik Johannes j.gajdosik at pke.at
Fri Jun 3 21:41:24 CEST 2016


Hi,

In my code I use CODEC_FLAG_TRUNCATED for mpeg4 decoding.
But this gives me 2 bugs in my program: a memleak an a missing decoded image.
I get the misbehaviour in all versions I have tested with, especially in versions 3.0.2 and 2.8.7.

In order to reproduce this behaviour I have written a fully working test program below.

Could you please tell me if I use ffmpeg correctly and if the bugs are my own fault or part of ffmpeg?

Yours,
Johannes Gajdosik






--------------------start of program-----------------------
/*

Compile with:
gcc -g ffmpeg_mpeg4_decoding_bugs.c -o ffmpeg_mpeg4_decoding_bugs -lavcodec -lavutil

This program intends to demonstrate 2 bugs in the ffmpeg mpeg4 encoder:
a memleak and failure to flush when CODEC_FLAG_TRUNCATED is used.
It works like this:

1) Generate a certain number of mpeg4-frames with a unique image contents.
2) Decode the encoded frames and check the image contents.
   Step 2 can be done repeatedly in order to demonstrate the memleak.
3) release the encoded images

Basically the program works: The encoded images can be decoded
and the image contents is successfully checked.
But there are 2 bugs.

How to reproduce the bugs:

A) Call './ffmpeg_mpeg4_decoding_bugs -flag_truncated -verbosity 5'
You will see the msg "ERROR: only 4 pictures decoded, 1 picture(s) missing".
The last image was not decoded although the decoder was flushed
with a NULL-packet.

B) Call './ffmpeg_mpeg4_decoding_bugs -flag_truncated -verbosity 0 -nr_of_decoding_runs 1000000'
You can watch the memleak with 'top'.
Each time a the decoder is released you lose about 3k, that is approximately
the size of an I-Frame. Alternatively you can use valgrind:
valgrind --leak-check=full ./ffmpeg_mpeg4_decoding_bugs -flag_truncated

Note that without -flag_truncated these 2 errors do not show up.

*/


#include <libavcodec/avcodec.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

  /* program parameters: */
static int nr_of_images = 5;
static int nr_of_decoding_runs = 1;
static int flag_truncated = 0;
static int verbosity = 1;


  /* encoded images: */
struct EncodedImage {
  unsigned char *data;
  int size;
};
static struct EncodedImage *encoded_images = 0;



  /* in a grey image fill a rectangle with 'val' */
static
void FillRect(unsigned char *im,int width,
              int x,int y,int size_x,int size_y,
              unsigned char val) {
  im += y*width+x;
  while (--size_y >= 0) {
    memset(im,val,size_x);
    im += width;
  }
}

  /* get average gray value inside given rectangle */
unsigned char GetRectVal(const unsigned char *im,int width,
                         int x,int y,int size_x,int size_y) {
  unsigned int rval = 0;
    /* ignore rectangle borders: */
  x++;y++;
  size_x -= 2;
  size_y -= 2;
  im += y*width+x;
    /* sum up all pixel values: */
  for (y=0;y<size_y;y++,im+=width) {
    for (x=0;x<size_x;x++) {
      rval += im[x];
    }
  }
    /* and divide by number of pixels: */
  return (unsigned char)(rval / (size_y * size_x));
}

  /* fill yuv420 image with a pattern that encodes 'number' */
static
void EncodeBWpatternIntoYuvImage(unsigned char *im,
                                 int width,int height,
                                 unsigned int number) {
  const int w4 = width / 4;
  const int h4 = height / 4;
  int i,j;
    /* set u,v to neutral color */
  memset(im+width*height,128,width*height/2);
    /* fill image with a B/W pattern that encodes 'number' */
  for (j=3;j>=0;j--) {
    for (i=3;i>=0;i--) {
      FillRect(im,width,i*w4,j*h4,w4,h4,(number&1)?255:0);
      number >>= 1;
    }
  }
}

  /* decode the number that was encoded in EncodeBWpatternIntoYuvImage: */
static
unsigned int DecodeBWpatternFromYuv420(const unsigned char *im,
                                       int width,int height) {
  unsigned int image_val = 0;
    /* extract value of the B/W-pattern: */
  const int w4 = width / 4;
  const int h4 = height / 4;
  int i,j;
  for (j=0;j<4;j++) {
    for (i=0;i<4;i++) {
      const unsigned char val = GetRectVal(im,width,i*w4,j*h4,w4,h4);
      image_val <<= 1;
      if (val >= 128) image_val |= 1;
    }
  }
  return image_val;
}





  /* populate encoded_images: */
static
void EncodeImages(void) {

    /* ffmpeg data structures: */
  AVFrame *frame = av_frame_alloc();
  AVCodec *const codec = avcodec_find_encoder(AV_CODEC_ID_MPEG4);
  AVCodecContext *const codec_context = avcodec_alloc_context3(codec);

    /* image to encode: */
  const int width = 512;
  const int height = 512;
  unsigned char *const im = (unsigned char*)malloc(width*height*3/2);

  unsigned int image_nr;
  int got_output;
  

  if (frame == 0) abort();
  if (codec == 0) abort();
  if (codec_context == 0) abort();
  if (im == 0) abort();

  encoded_images = (struct EncodedImage*)
                     calloc(nr_of_images,sizeof(struct EncodedImage));
  if (encoded_images == 0) abort();

    /* encoding parameters: */
  codec_context->bit_rate_tolerance = 4000000;
  codec_context->bit_rate = 500000;
  codec_context->width = frame->width = width;
  codec_context->height = frame->height = height;
  codec_context->time_base.num = 1;
  codec_context->time_base.den = 25;
  codec_context->gop_size = 5;
  codec_context->max_b_frames = 0;
  frame->format = AV_PIX_FMT_YUV420P;
  codec_context->pix_fmt = (enum AVPixelFormat)frame->format;

  if (avcodec_open2(codec_context,codec,0) < 0) abort();

  for (image_nr=0;image_nr<nr_of_images;image_nr++) {
    AVPacket pkt;
    EncodeBWpatternIntoYuvImage(im,width,height,image_nr);
    frame->data[0] = im;
    frame->data[1] = frame->data[0] + (frame->width * frame->height);
    frame->data[2] = frame->data[1] + (frame->width * frame->height) / 4;
    frame->linesize[0] = width;
    frame->linesize[2] = frame->linesize[1] = frame->linesize[0]/2;
    frame->pts = image_nr;

    av_init_packet(&pkt);
    pkt.data = 0;
    pkt.size = 0;
    
    if (0 > avcodec_encode_video2(codec_context,&pkt,frame,
                                  &got_output)) abort();
    if (!got_output) abort();
    if (pkt.size <= 0 || pkt.size > 100000) abort();

    if (verbosity>2) printf("image %d encoded, size: %d\n",image_nr,pkt.size);

      /* save encoded image: */
    encoded_images[image_nr].data
      = (unsigned char*)malloc(pkt.size+AV_INPUT_BUFFER_PADDING_SIZE);
    if (encoded_images[image_nr].data == 0) abort();
    encoded_images[image_nr].size = pkt.size;
    memcpy(encoded_images[image_nr].data,pkt.data,pkt.size);
      /* pad with zeros: */
    memset(encoded_images[image_nr].data+pkt.size,0,
           AV_INPUT_BUFFER_PADDING_SIZE);

      /* cleanup */
    av_free_packet(&pkt);
  }
    /* cleanup */
  free(im);
  avcodec_close(codec_context);
  av_free(codec_context);
  av_frame_free(&frame);
  if (verbosity>1) printf("%d images encoded\n",nr_of_images);
}

  /* cleanup encoded_images */
static void FreeImages(void) {
  unsigned int image_nr;
  for (image_nr=0;image_nr<nr_of_images;image_nr++) {
    free(encoded_images[image_nr].data);
    if (verbosity>4) printf("image %d released\n",image_nr);
  }
  free(encoded_images);
  if (verbosity>1) printf("%d images released\n",nr_of_images);
}



void DecodeImages(void) {
    /* ffmpeg data structures: */
  AVFrame *picture = av_frame_alloc();
  AVCodec *const codec = avcodec_find_decoder(AV_CODEC_ID_MPEG4);
  AVCodecContext *const codec_context = avcodec_alloc_context3(codec);

  unsigned int frame_nr;
  unsigned int picture_nr = 0;
  unsigned int image_val;
  int got_pic;

  if (picture == 0) abort();
  if (codec == 0) abort();
  if (codec_context == 0) abort();

  codec_context->codec_type = codec->type;
  codec_context->codec_id = codec->id;
  if (flag_truncated) codec_context->flags |= CODEC_FLAG_TRUNCATED;

  if (avcodec_open2(codec_context,codec,0) < 0) abort();
  
  for (frame_nr=0;;frame_nr++) {
    AVPacket pkt;
    av_init_packet(&pkt);
    if (frame_nr<nr_of_images) {
      if (verbosity>4) printf("decoding frame %d\n",frame_nr);
      pkt.data = encoded_images[frame_nr].data;
      pkt.size = encoded_images[frame_nr].size;
    } else {
        /* decode NULL-packet in order to flush the decoder: */
      if (verbosity>2) printf("flushing decoder\n");
      pkt.data = 0;
      pkt.size = 0;
    }
    do {
      const int len = avcodec_decode_video2(codec_context,picture,
                                            &got_pic,&pkt);
      if (verbosity>3) printf("avcodec_decode_video2(%d) "
                              "returned %d, got_pict: %d\n",
                              pkt.size,len,got_pic);
      if (len < 0) break;
      if (got_pic) {
        if (picture->format != AV_PIX_FMT_YUV420P) abort();
        image_val = DecodeBWpatternFromYuv420(picture->data[0],
                                              picture->linesize[0],
                                              picture->height);
        if (image_val != picture_nr) {
          if (verbosity>0) printf("ERROR: picture %u has bad pattern %u\n",
                                  picture_nr,image_val);
        } else {
          if (verbosity>4) printf("picture %u: pattern ok\n",picture_nr);
        }
        picture_nr++;
      }
      if (pkt.data) {
        pkt.data += len;
        pkt.size -= len;
      }
    } while (pkt.size > 0);
    av_free_packet(&pkt);
      /* exit only after the flushing is complete: */
    if (got_pic == 0 && frame_nr >= nr_of_images) break;
  }
  if (picture_nr < nr_of_images) {
    if (verbosity>0) printf("ERROR: only %d pictures decoded,"
                            " %d picture(s) missing\n",
                            picture_nr,nr_of_images-picture_nr);
  }
  
    /* cleanup */
  avcodec_close(codec_context);
  av_free(codec_context);
  av_frame_free(&picture);
}


static
void PrintUsage(const char *progname) {
  printf("Usage: %s [-nr_of_images <nr>] [-nr_of_decoding_runs <nr>]"
         " [-flag_truncated] [-verbosity <0..5>]\n\n",
         progname);
}

int main(int argc,char *argv[]) {
  int i;
  for (i=1;i<argc;i++) {
    if (0 == strcmp(argv[i],"-nr_of_images")) {
      if (++i >= argc ||
          1 != sscanf(argv[i],"%d",&nr_of_images) ||
          nr_of_images <= 0) {
        fprintf(stderr,"nr_of_images: integer expected\n");
        return 1;
      }
    } else
    if (0 == strcmp(argv[i],"-nr_of_decoding_runs")) {
      if (++i >= argc ||
          1 != sscanf(argv[i],"%d",&nr_of_decoding_runs) ||
          nr_of_decoding_runs <= 0) {
        fprintf(stderr,"nr_of_decoding_runs: integer expected\n");
        return 1;
      }
    } else
    if (0 == strcmp(argv[i],"-flag_truncated")) {
      flag_truncated = 1;
    } else
    if (0 == strcmp(argv[i],"-verbosity")) {
      if (++i >= argc ||
          1 != sscanf(argv[i],"%d",&verbosity) ||
          verbosity < 0) {
        fprintf(stderr,"verbosity: integer expected\n");
        return 1;
      }
    } else
    {
      fprintf(stderr,"unknown option: \"%s\"\n",argv[i]);
      PrintUsage(argv[0]);
      return 1;
    }
  }

  printf("program parameters:\n"
         "  nr_of_images: %d\n"
         "  nr_of_decoding_runs: %d\n"
         "  flag_truncated: %d\n"
         "  verbosity: %d\n\n",
         nr_of_images,nr_of_decoding_runs,flag_truncated,verbosity);

  avcodec_register_all();
  EncodeImages();
  for (i=0;i<nr_of_decoding_runs;i++) {
    if (verbosity>0) printf("decoding test, run %d\n",i);
    DecodeImages();
  }
  FreeImages();
  printf("bye.\n");
  return 0;
}


More information about the Libav-user mailing list