Subtle Development Space
Subtle Development Space

Reputation: 1254

Memory leak in jpeg compression. Bug or my mistake?

I wrote an npm module for capturing webcam input on linux. The captured frame in yuyv format is converted to rgb24 and after compressed to a jpeg image. In the jpeg compression there appears to be a memory leak. So the usage of memory increases continuously.

 Image* rgb24_to_jpeg(Image *img, Image *jpeg) { // img = RGB24
    jpeg_compress_struct cinfo;
    jpeg_error_mgr jerr;
    cinfo.err = jpeg_std_error(&jerr);
    jerr.trace_level = 10;
    jpeg_create_compress(&cinfo);

    unsigned char *imgd = new unsigned char[img->size];
    long unsigned int size = 0;
    jpeg_mem_dest(&cinfo, &imgd, &size);

    cinfo.image_width = img->width;
    cinfo.image_height = img->height;
    cinfo.input_components = 3;
    cinfo.in_color_space = JCS_RGB;
    jpeg_set_defaults(&cinfo);

    jpeg_set_quality(&cinfo, 100, true);
    jpeg_start_compress(&cinfo, true);
    int row_stride = cinfo.image_width * 3;
    JSAMPROW row_pointer[1];
    while (cinfo.next_scanline < cinfo.image_height) {
      row_pointer[0] = &img->data[cinfo.next_scanline * row_stride];
      jpeg_write_scanlines(&cinfo, row_pointer, 1);
    }
    jpeg_finish_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);
//    size += 512; // TODO: actual value to expand jpeg buffer... JPEG header?
    if (jpeg->data == NULL) {
      jpeg->data = (unsigned char *) malloc(size);
    } else {
      jpeg->data = (unsigned char *) realloc(jpeg->data, size);
    }
    memcpy(jpeg->data, imgd, size);
    delete[] imgd;
    jpeg->size = size;
    return jpeg;
  }

The rgb24 and jpeg buffers are reallocated on every cycle. So it looks like the leak is inside libjpeg layer. Is this true or I simply made a mistake somewhere in the code?

Note: the compressed image shall not be saved as a file, since the data might be used for live streaming.

Upvotes: 2

Views: 2262

Answers (2)

KronuZ
KronuZ

Reputation: 374

In my case I did not solve the issue with previous answer, there was no way to free the memory image pointer, the only way to do that was reserving enough memory to the image and that way the library will not reserve memory and I have the control over the memory and is on the same heap of my application and not on the library's heap, here is my example:

//previous code...
struct jpeg_compress_struct cinfo;

//reserving the enough memory for my image (width * height)
unsigned char* _image = (unsigned char*)malloc(Width * Height);

//putting the reserved size into _imageSize
_imageSize = Width * Height;

//call the function like this:
jpeg_mem_dest(&cinfo, &_image, &_imageSize);
................
//releasing the reserved memory
free(_image);

NOTE: if you put _imageSize = 0, the library will assume that you have not reserve memory and the own library will do it.. so you need to put in _imageSize the amount of bytes reserved in _image

That way you have total control over the reserved memory and you can release it whenever you want in your software..

Upvotes: -1

Rudolfs Bundulis
Rudolfs Bundulis

Reputation: 11934

You are using the jpeg_mem_dest in a wrong way - the second parameter is pointer to pointer to char because it is actually set by the library and then you must free it after you are done. Now you are initializing it with a pointer, it gets overwritten and you free the memory region allocated by the library but the original memory region is leaked.

This is how you should change your function:

Image* rgb24_to_jpeg(Image *img, Image *jpeg) { // img = RGB24
    jpeg_compress_struct cinfo;
    jpeg_error_mgr jerr;
    cinfo.err = jpeg_std_error(&jerr);
    jerr.trace_level = 10;
    jpeg_create_compress(&cinfo);

    unsigned char *imgd = 0;
    long unsigned int size = 0;
    cinfo.image_width = img->width;
    cinfo.image_height = img->height;
    cinfo.input_components = 3;
    cinfo.in_color_space = JCS_RGB;
    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, 100, true);
    jpeg_mem_dest(&cinfo, &imgd, &size); // imgd will be set by the library
    jpeg_start_compress(&cinfo, true);
    int row_stride = cinfo.image_width * 3;
    JSAMPROW row_pointer[1];
    while (cinfo.next_scanline < cinfo.image_height) {
        row_pointer[0] = &img->data[cinfo.next_scanline * row_stride];
        jpeg_write_scanlines(&cinfo, row_pointer, 1);
    }
    jpeg_finish_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);
    //    size += 512; // TODO: actual value to expand jpeg buffer... JPEG header?
    if (jpeg->data == NULL) {
        jpeg->data = (unsigned char *) malloc(size);
    } else if (jpeg->size != size) {
        jpeg->data = (unsigned char *) realloc(jpeg->data, size);
    }
    memcpy(jpeg->data, imgd, size);
    free(imgd); // dispose of imgd when you are done
    jpeg->size = size;
    return jpeg;
}

This snippet form jpeg_mem_dest explains the memory management:

  if (*outbuffer == NULL || *outsize == 0) {
    /* Allocate initial buffer */
    dest->newbuffer = *outbuffer = (unsigned char *) malloc(OUTPUT_BUF_SIZE);
    if (dest->newbuffer == NULL)
      ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 10);
    *outsize = OUTPUT_BUF_SIZE;
  }

So, if you pass a an empty pointer or a zero sized buffer the library will perform an allocation for you. Thus - another approach is also to set the size correctly and then you can use the originally supplied pointer

Upvotes: 2

Related Questions