zocker_160
Posts: 2
Joined: Mon Sep 25, 2023 5:46 pm

V4L2 MJPEG decoding using /dev/video10

Mon Sep 25, 2023 6:13 pm

Dear Pi community (and 6by9 -.-),

I am trying to convert an MJPEG stream to some kind of RGB format and I am stuck with an error, that I don't understand why it is happening.

When checking device capabilities then /dev/video10 should be able to do what I need.
The problem is that when trying to request an OUTPUT buffer, I get an error that it cannot allocate memory.

I am running on a Raspberry Pi 4, but this code needs to work on a Pi3 as well.

Source code below and sample image attached:

Code: Select all

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

//#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>

#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/param.h>

#include <linux/videodev2.h>

void get_supported_formats(int videofd) {
    int i, x;
    struct v4l2_fmtdesc fmtdesc;

    /* Get supported Input Formats */
    //memset(&fmtdesc, 0, sizeof(fmtdesc));
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;

    i = 0;
    do {
        fmtdesc.index = i;
        if ((x=ioctl(videofd, VIDIOC_ENUM_FMT, &fmtdesc)) == 0) {
            printf("OUT (%d): %s\n", i, fmtdesc.description);
        }
        i++;
    } while (x == 0);

    /* Get supported Output Formats */
    //memset(&fmtdesc, 0, sizeof(fmtdesc));
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;

    i = 0;
    do {
        fmtdesc.index = i;
        if ((x=ioctl(videofd, VIDIOC_ENUM_FMT, &fmtdesc)) == 0) {
            printf("CAP (%d): %s\n", i, fmtdesc.description);
        }
        i++;
    } while (x == 0);
}


void decodeJPEG(const void* data, size_t size, size_t width, size_t height) {
    //int fd = open("/dev/video31", O_RDWR);
    int fd = open("/dev/video10", O_RDWR);
    if (fd == -1) {
        perror("Failed to open V4L2 device");
        exit(EXIT_FAILURE);
    }

    get_supported_formats(fd);
    //return;

    // setting IO formats

    struct v4l2_format fmt;
    fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
    fmt.fmt.pix.width = width;
    fmt.fmt.pix.height = height;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;

    if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
        perror("Failed to set V4L2 OUT format");
        exit(EXIT_FAILURE);
    }

    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB565;

    if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
        perror("Failed to set V4L2 CAP format");
        exit(EXIT_FAILURE);
    }

    // requesting and setting up IO buffers

    struct v4l2_requestbuffers reqbuf;
    reqbuf.memory = V4L2_MEMORY_MMAP;
    reqbuf.count = 1;

    reqbuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
    if (ioctl(fd, VIDIOC_REQBUFS, &reqbuf) == -1) {
        perror("Failed to request OUTPUT buffer");
        exit(EXIT_FAILURE);
    }

    reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    if (ioctl(fd, VIDIOC_REQBUFS, &reqbuf) == -1) {
        perror("Failed to request CAPTURE buffer");
        exit(EXIT_FAILURE);
    }


    struct v4l2_plane outPlane;
    struct v4l2_buffer outBuffer;
    outBuffer.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
    outBuffer.memory = V4L2_MEMORY_MMAP;
    outBuffer.index = 0;
    outBuffer.length = 1;
    outBuffer.m.planes = &outPlane;

    if (ioctl(fd, VIDIOC_QUERYBUF, &outBuffer) == -1) {
        perror("Failed to query OUTPUT buffer");
        exit(EXIT_FAILURE);
    }

    void* outBufferStart = mmap(NULL, 
        outPlane.length,
        PROT_READ | PROT_WRITE, MAP_SHARED, 
        fd, 
        outPlane.m.mem_offset
    );


    struct v4l2_plane capPlane;
    struct v4l2_buffer capBuffer;
    capBuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    capBuffer.memory = V4L2_MEMORY_MMAP;
    capBuffer.index = 0;
    capBuffer.length = 1;
    capBuffer.m.planes = &capPlane;

    if (ioctl(fd, VIDIOC_QUERYBUF, &capBuffer) == -1) {
        perror("Failed to query CAPTURE buffer");
        exit(EXIT_FAILURE);
    }

    void* capBufferStart = mmap(NULL, 
        capPlane.length,
        PROT_READ | PROT_WRITE, MAP_SHARED, 
        fd, 
        capPlane.m.mem_offset
    );


    {
        int type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
        if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) {
            perror("Failed to STEAMON OUT buffer");
            exit(EXIT_FAILURE);
        }

        type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
        if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) {
            perror("Failed to STEAMON CAP buffer");
            exit(EXIT_FAILURE);
        }
    }

    printf("starting loop \n");

    int bytes;

    do {
        printf("Q CAP buffer \n"); 
        if (ioctl(fd, VIDIOC_QBUF, &capBuffer) == -1) {
            perror("Q CAP failed");
            exit(EXIT_FAILURE);
        }

        printf("copy data \n");

        bytes = MIN(size, outPlane.length);
        memcpy(outBufferStart, data, bytes);
        outPlane.bytesused = bytes;

        printf("plane size: %d, image size: %d \n", outPlane.length, size);

        printf("Q OUT buffer \n");
        if (ioctl(fd, VIDIOC_QBUF, &outBuffer) == -1) {
            perror("Q OUT failed");
            exit(EXIT_FAILURE);
        }

        printf("DQ CAP buffer \n");
        if (ioctl(fd, VIDIOC_DQBUF, &capBuffer) == -1) {
            perror("DQ CAP failed");
            exit(EXIT_FAILURE);
        }

        int encodedSize = capPlane.bytesused;
        printf("plane size: %d, encoded size: %d \n", outPlane.length, encodedSize);

        // FIXME this is just for testing
        FILE* fp = fopen("out2.raw", "wb");
        fwrite(capBufferStart, encodedSize, 1, fp);
        fclose(fp);

        printf("DQ OUT buffer \n");
        if (ioctl(fd, VIDIOC_DQBUF, &outBuffer) == -1) {    
            perror("DQ OUT failed");
            exit(EXIT_FAILURE);
        }

        // FIXME just for testing
        bytes = 0;

    } while (bytes != 0);

    printf("exiting loop \n");

    close(fd);

    return;

    {
        int type = V4L2_CAP_VIDEO_OUTPUT_MPLANE;
        if (ioctl(fd, VIDIOC_STREAMOFF, &type) == -1) {
            perror("Failed to STEAMOFF OUT buffer");
            exit(EXIT_FAILURE);
        }

        type = V4L2_CAP_VIDEO_CAPTURE_MPLANE;
        if (ioctl(fd, VIDIOC_STREAMOFF, &type) == -1) {
            perror("Failed to STEAMOFF CAP buffer");
            exit(EXIT_FAILURE);
        }
    }
}


int main() {

    FILE* image;
    size_t imageSize;

    image = fopen("sample.jpeg", "rb");
    if (!image) {
        perror("failed to open image file");
        exit(EXIT_FAILURE);
    }

    fseek(image, 0, SEEK_END);
    imageSize = ftell(image);
    fseek(image, 0, SEEK_SET);

    void* imageData = malloc(imageSize);

    fread(imageData, 1, imageSize, image);

    fclose(image);


    //decodeJPEG(imageData, imageSize, 800, 450);
    decodeJPEG(imageData, imageSize, 160, 384);

    free(imageData);

    return 0;
}
You can simply compile it with "gcc v4ltest.c".

The output I am getting:

Code: Select all

$ gcc v4ltest.c && ./a.out
OUT (0): H.264
OUT (1): Motion-JPEG
CAP (0): Planar YUV 4:2:0
CAP (1): Planar YVU 4:2:0
CAP (2): Y/UV 4:2:0
CAP (3): Y/VU 4:2:0
CAP (4): Y/CbCr 4:2:0 (128b cols)
CAP (5): 16-bit RGB 5-6-5
----
Failed to request OUTPUT buffer: Cannot allocate memory
Any help would be highly appreciated.
Attachments
sample.jpeg
sample.jpeg (6.46 KiB) Viewed 2840 times

User avatar
radiolistener
Posts: 622
Joined: Thu Aug 03, 2023 6:49 am

Re: V4L2 MJPEG decoding using /dev/video10

Mon Sep 25, 2023 10:39 pm

here is output on RPI4 raspios aarch64:
Starting program: /home/pi/Documents/TEST/test-jpeg
OUT (0): H.264
OUT (1): Motion-JPEG
CAP (0): Planar YUV 4:2:0
CAP (1): Planar YVU 4:2:0
CAP (2): Y/UV 4:2:0
CAP (3): Y/VU 4:2:0
CAP (4): Y/CbCr 4:2:0 (128b cols)
CAP (5): 16-bit RGB 5-6-5
starting loop
Q CAP buffer
copy data
plane size: 524288, image size: 6615
Q OUT buffer
DQ CAP buffer
^C
Program received signal SIGINT, Interrupt.
ioctl () at ../sysdeps/unix/sysv/linux/aarch64/ioctl.S:25
25 ../sysdeps/unix/sysv/linux/aarch64/ioctl.S: No such file or directory.
(gdb) bt
#0 ioctl () at ../sysdeps/unix/sysv/linux/aarch64/ioctl.S:25
#1 0x00000055555510f4 in decodeJPEG (data=0x5555564490, size=6615, width=160, height=384) at test-jpeg.c:180
#2 0x0000005555551280 in main () at test-jpeg.c:249
according to gdb it hungs at line 180:

Code: Select all

if (ioctl(fd, VIDIOC_DQBUF, &capBuffer) == -1) {

6by9
Raspberry Pi Engineer & Forum Moderator
Raspberry Pi Engineer & Forum Moderator
Posts: 15272
Joined: Wed Dec 04, 2013 11:27 am
Location: ZZ9 Plural Z Alpha, aka just outside Cambridge.

Re: V4L2 MJPEG decoding using /dev/video10

Tue Sep 26, 2023 8:56 am

Being unable to allocate memory sounds like you've not got a sufficiently sized CMA heap. The default of having "dtoverlay=vc4-kms-v3d" in config.txt should mean that you have a 256MB CMA heap which is generally sufficient.


Following the V4L2 Stateful Decoder Interface description, step 4 of initialisation
This step only applies to coded formats that contain resolution information in the stream. Continue queuing/dequeuing bytestream buffers to/from the OUTPUT queue via VIDIOC_QBUF() and VIDIOC_DQBUF(). The buffers will be processed and returned to the client in order, until required metadata to configure the CAPTURE queue are found. This is indicated by the decoder sending a V4L2_EVENT_SOURCE_CHANGE event with changes set to V4L2_EVENT_SRC_CH_RESOLUTION.
JPEG/MJPEG has resolution information in the stream.
I suspect that is why radiolistener is seeing it stall.
Software Engineer at Raspberry Pi Ltd. Views expressed are still personal views.
I'm not interested in doing contracts for bespoke functionality - please don't ask.

zocker_160
Posts: 2
Joined: Mon Sep 25, 2023 5:46 pm

Re: V4L2 MJPEG decoding using /dev/video10

Tue Sep 26, 2023 4:39 pm

Thank you to both of you for the answers.

I retried this on two Pis today, one Pi3 and one Pi4 4GB and both have this issue.

When checking meminfo, it seems around 510MB are free, which should be plenty?

Code: Select all

$ cat /proc/meminfo | grep Cma
CmaTotal:         524288 kB
CmaFree:          510036 kB
My Pi3 only has 93 MB, so I fear that the chances of getting this work seem low?

The code getting stuck for @radiolistener is probably because my implementation is not correct, which is not surprising given that I could not even get that far on my Pi :(.

My goal is to decode the MJPEG frames on the GPU, because doing that on the CPU takes around 40ms per frame, which is way too slow sadly.

Any ideas what I could do?

EDIT: nobody? :(

Return to “Graphics programming”