Search code examples
c++mjpegvideo4linux

mjpeg to raw rgb24 with video4linux


I'm writing a c++ webcam viewer using video4linux. I need a RGB24 output (interleaved R8B8G8) for displaying. I'm able to get video input for almost all low-resolution webcam, using YUYV, GREY8 or RGB24. But I need to get input also from high-resolution webcams, that use MJPEG for compression when high framerate is needed.

I'm able to get MJPEG stream using V4L2_PIX_FMT_MJPEG as pixel format, but received framebuffer is compressed.

How can I quickly convert it to RGB24?

Can I use libjpeg for this?


Solution

  • The quickest solution I've found is decode_jpeg_raw from mjpegtools which decode jpeg data to planar YUV420. Then the conversion from yuv420 to rgb24 is done by this function:

    inline int clip(int value) {
        return (value > 255) ? 255 : (value < 0) ? 0 : value;
    }
    
    static void yuv420_to_rgb24(
    /* luminance (source) */const uint8_t* const y
    /* u chrominance (source) */, const uint8_t* u
    /* v chrominance (source) */, const uint8_t* v
    /* rgb interleaved (destination) */, uint8_t* const dst
    /* jpeg size */, int const size
    /* image width */, int const width) {
    
        const int lineSize = width * 3;
    
        uint8_t* r1 = dst;
        uint8_t* g1 = r1 + 1;
        uint8_t* b1 = r1 + 2;
    
        uint8_t* r2 = r1 + lineSize;
        uint8_t* g2 = r2 + 1;
        uint8_t* b2 = r2 + 2;
    
        const uint8_t* y1 = y;
        const uint8_t* y2 = y + width;
    
        uint8_t* const end = r1 + size;
    
        int c1 = 0;
        int c2 = 0;
        int e = 0;
        int d = 0;
    
        while (r1 != end) {
            uint8_t* const lineEnd = r2;
            /* line by line */
            while (r1 != lineEnd) {
                /* first pixel */
                c1 = *y1 - 16;
                c2 = *y2 - 16;
                d = *u - 128;
                e = *v - 128;
    
                *r1 = clip(c1 + ((454 * e) >> 8));
                *g1 = clip(c1 - ((88 * e + 183 * d) >> 8));
                *b1 = clip(c1 + ((359 * d) >> 8));
    
                *r2 = clip(c2 + ((454 * e) >> 8));
                *g2 = clip(c2 - ((88 * e + 183 * d) >> 8));
                *b2 = clip(c2 + ((359 * d) >> 8));
    
                r1 += 3;
                g1 += 3;
                b1 += 3;
    
                r2 += 3;
                g2 += 3;
                b2 += 3;
    
                ++y1;
                ++y2;
    
                /* second pixel */
    
                c1 = *y1 - 16;
                c2 = *y2 - 16;
                d = *u - 128;
                e = *v - 128;
    
                *r1 = clip(c1 + ((454 * e) >> 8));
                *g1 = clip(c1 - ((88 * e + 183 * d) >> 8));
                *b1 = clip(c1 + ((359 * d) >> 8));
    
                *r2 = clip(c2 + ((454 * e) >> 8));
                *g2 = clip(c2 - ((88 * e + 183 * d) >> 8));
                *b2 = clip(c2 + ((359 * d) >> 8));
    
                r1 += 3;
                g1 += 3;
                b1 += 3;
    
                r2 += 3;
                g2 += 3;
                b2 += 3;
    
                ++y1;
                ++y2;
    
                ++u;
                ++v;
            }
            r1 += lineSize;
            g1 += lineSize;
            b1 += lineSize;
            r2 += lineSize;
            g2 += lineSize;
            b2 += lineSize;
            y1 += width;
            y2 += width;
        }
    }