Enable mcux jpegdec and pngdec video driver#101883
Enable mcux jpegdec and pngdec video driver#101883KATE-WANG-NXP wants to merge 12 commits intozephyrproject-rtos:mainfrom
Conversation
Does the RT700-EVK supports a camera that can output jpeg (e.g. ov5640) so that we can demo on the capture sample ? |
Or maybe even just a display (either display api directly or LVGL based) of a fixed / hardcoded jpeg frame stored directly into the sample code, that would allow to very easily validate a jpeg decoding and can be reused for all other upcoming jpeg decoder |
It will be much easier with libMP but currently I don't have enough bandwidth to implement jpegparse, jpegdec, tcpserversrc, tcpserversink and pull-push mode support ... :-) |
Stored in |
|
Stored in video-sw-generator is a good idea, jpeg will be a supported format like any other formats |
Excellent idea !!! |
We do not have a camera on RT700 that can output jpeg format frame, there is a PR in progress that introduces a usb camera device that can do that 728e508 |
Add support for compressed video formats (JPEG and PNG) in the software video generator driver. This enables testing of compressed video formats without requiring actual hardware. The compressed formats only support a fixed 320x160 resolution and provide two frames (normal and horizontally flipped) for testing. Signed-off-by: Kate Wang <yumeng.wang@nxp.com>
Add support for NXP JPEG decoder hardware module. This driver provides memory-to-memory (M2M) video transformation capability to decode JPEG compressed images into raw pixel formats. Key features: - Supports JPEG input format (VIDEO_PIX_FMT_JPEG) - Resolution support: 64x64 to 8192x8192 pixels (16-pixel step) - Automatic format detection from JPEG header parsing - No color space conversion (output format matches JPEG encoding) - Interrupt-driven operation with input/output buffer queues - Configurable output buffer pitch for flexible memory layouts Signed-off-by: Kate Wang <yumeng.wang@nxp.com>
Add support for NXP PNG decoder hardware module. This driver provides memory-to-memory (M2M) video transformation capability to decode PNG compressed images into raw pixel formats. Key features: - Supports PNG input format (VIDEO_PIX_FMT_PNG) - Resolution support: 1x1 to 1024x65535 pixels (1-pixel step) - Fixed ABGR8888 output format (VIDEO_PIX_FMT_ABGR32) - Interrupt-driven operation with input/output buffer queues - 8-byte buffer alignment requirement Signed-off-by: Kate Wang <yumeng.wang@nxp.com>
Add support for more pixel formats that are supported by the DCNano on mimxrt700_evk: - PIXEL_FORMAT_RGB_888: 24-bit RGB format with 8 bits per component - PIXEL_FORMAT_BGR_888: 24-bit BGR format with 8 bits per component - PIXEL_FORMAT_ABGR_8888: 32-bit ABGR with alpha channel - PIXEL_FORMAT_RGBA_8888: 32-bit RGBA with alpha channel - PIXEL_FORMAT_BGRA_8888: 32-bit BGRA with alpha channel Signed-off-by: Kate Wang <yumeng.wang@nxp.com>
Add MCUX SDK driver component mappings for the JPEG and PNG decoder drivers. Signed-off-by: Kate Wang <yumeng.wang@nxp.com>
Add device tree nodes for the JPEG and PNG hardware decoder modules on the RT7xx SoC. Signed-off-by: Kate Wang <yumeng.wang@nxp.com>
Enable the JPEG and PNG hardware decoder modules on the mimxrt700_evk board Signed-off-by: Kate Wang <yumeng.wang@nxp.com>
Add support for NXP JPEG and PNG hardware decoders to the video capture sample application for the mimxrt700_evk board. Changes include: - Add transform_nxp_jpegdec.c with JPEG decoder integration and optional software color space conversion from NV12 to RGB565 - Add transform_nxp_pngdec.c with PNG decoder integration supporting ABGR8888 output format - Add board-specific configuration and overlay files for mimxrt700_evk to enable PNG decoder by default (JPEG decoder configuration available but commented out) - Add Kconfig option VIDEO_NXP_JPEGDEC_CSC to enable software-based color space conversion for JPEG decoder output - Update sample.yaml to include mimxrt700_evk test configurations with video-sw-generator snippet and rk055hdmipi4ma0 shield Signed-off-by: Kate Wang <yumeng.wang@nxp.com>
b218acb to
babe8ba
Compare
|
Hi @ngphibang, I also enabled the nxp pngdec video driver and enabled it in the video capture example. Please have a test :-) |
|
| struct mcux_jpegdec_data *data = dev->data; | ||
|
|
||
| *fmt = fmt->type == VIDEO_BUF_TYPE_INPUT ? data->m2m.in.fmt : data->m2m.out.fmt; | ||
|
|
There was a problem hiding this comment.
format->pitch and size is determined by the driver (the one who know if need padding or not) and should be return back to the application. You could take a look at other video drivers.
Also, could use video_estimate_fmt_size() helper to compute it.
| /* Update pitch value. */ | ||
| data->m2m.out.fmt.pitch = fmt->pitch; | ||
| data->m2m.out.fmt.size = fmt->pitch * fmt->height; | ||
| if (data->m2m.out.fmt.pixelformat == VIDEO_PIX_FMT_NV12) { | ||
| data->m2m.out.fmt.size = fmt->pitch * fmt->height * 2U; | ||
| } else { | ||
| data->m2m.out.fmt.size = fmt->pitch * fmt->height; | ||
| } |
There was a problem hiding this comment.
Application just sets the pixelformat, width and height.
format->pitch and size is determined / decided by the driver (the one who know if need padding or not) and should be return back to the application. You could take a look at other video drivers.
video_estimate_fmt_size() helper can be used to compute it.
| } else { | ||
| caps->format_caps = mcux_jpegdec_in_fmts; | ||
| } | ||
|
|
There was a problem hiding this comment.
buf_align is not merged yet, it's still under review
JarmouniA
left a comment
There was a problem hiding this comment.
Submit JPEGdec and PNGdec drivers in 2 separate PRs.
I honestly don't understand this desire to cram things that can be submitted independently in one huge unreviewable mess.
| POWER_ApplyPD(); | ||
| #endif | ||
|
|
||
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(jpegdec)) |
|
|
||
| capabilities->y_resolution = config->dpi_config.panelHeight; | ||
| capabilities->x_resolution = config->dpi_config.panelWidth; | ||
| #if DT_ENUM_IDX_OR(DT_NODELABEL(lcdif), version, 0) == 1 |
| /** | ||
| * @code{.unparsed} | ||
| * | Aaaaaaaa Bbbbbbbb Gggggggg Rrrrrrrr | ... | ||
| * | Bbbbbbbb Gggggggg Rrrrrrrr Aaaaaaaa | ... |
There was a problem hiding this comment.
Fixes should not be in the same commit as additions.
Also the fix is kind of unrelated to the PR, so it should be submitted in a separate one.
Sorry about that, I put it in the same PR for phibang to test on these m2m video components, I think I'll convert to draft then |
| static uint8_t BYTECLIP(int val) | ||
| { | ||
| if (val < 0) { | ||
| return 0U; | ||
| } else if (val > 255) { | ||
| return 255U; | ||
| } else { | ||
| return (uint8_t)val; | ||
| } | ||
| } |
There was a problem hiding this comment.
How about using the well-known CLAMP(val, 0, 255) macro directly?
| } | ||
| } | ||
|
|
||
| static void Convert_yuv420_to_rgb565(uint16_t width, uint16_t height, uint32_t yAddr, |
There was a problem hiding this comment.
You might be interested in lower-case everywhere for function and variable names.
There was a problem hiding this comment.
Having an app_ prefix as done elsewhere also seems like a good idea, but maybe not critical.
| #ifdef CONFIG_VIDEO_NXP_JPEGDEC_CSC | ||
| if (ret < 0) { | ||
| return ret; | ||
| } | ||
|
|
||
| /* The transfromed format shall be RGB565 for display panel to show. */ | ||
| out_fmt->pixelformat = VIDEO_PIX_FMT_RGB565; | ||
| out_fmt->pitch = out_fmt->width * 2U; | ||
|
|
||
| return 0; | ||
| #else | ||
| return ret; | ||
| #endif |
There was a problem hiding this comment.
#ifdef THIS_PARTICULAR_DRIVER means that every driver will need custom application code.
Better use generic Kconfig's and fill them in each board prj.conf.
I think we need to discuss how to make this possible to be vendor-neutral, as this is not going to work at scale.
This is also one reason for #98514: reduce the amount of custom application code.
There was a problem hiding this comment.
Interestingly, Linux support for compression engine goes a lot through Gstreamer, which is same use-case as libZMP.
| #define JPEG_FRAME_SIZE 0x522 /* Fixed size for each JPEG frame */ | ||
| #define PNG_FRAME_SIZE 0x164 /* Fixed size for each PNG frame */ |
There was a problem hiding this comment.
Maybe better to get the size directly from the array by sizeof(jpeg_frame_buffers) / 2 ? This is less error-prone (as we don't need to manually count the array items and don't need to change the macro when the array changes).
| }; | ||
|
|
||
| /* Static JPEG frame buffers. 2 320x160 color-bar pattern frames, h-flipped. */ | ||
| static uint8_t jpeg_frame_buffers[2U][JPEG_FRAME_SIZE] = { |
There was a problem hiding this comment.
This can be flatten to static uint8_t jpeg_frame_buffers[] = {...}; to avoid giving the size
There was a problem hiding this comment.
I would even go with one frame per variable, making it very trivial and flexible: compressed formats typically give buffers of each different size.
There was a problem hiding this comment.
Or even better: I would recommend to not store the buffer directly as hex in the source as it is a lot more difficult to inspect the image this way, while .jpeg and .png file are easy to inspect and modify.
How about using this for generating the buffers?
xxd -i frame.jpeg >jpeg_frame.hxxd -i frame_hflip.jpeg >jpeg_frame_hflip.h
See #101883 (comment) for an example of CMakeLists.txt integration. I can provide and test an example CMakeLists.txt snippet if that helps.
You can then include it from the middle of an array for instance.
/* Static JPEG frame buffers. */
static uint8_t jpeg_frame_buffer[] = {
#include "jpeg_frame.h"
};
static uint8_t jpeg_frame_hflip_buffer[] = {
#include "jpeg_frame_hflip.h"
};
/* Static PNG frame buffers. */
static uint8_t png_frame_buffer[] = {
#include "png_frame.h"
};
static uint8_t png_frame_hflip_buffer[] = {
#include "png_frame_hflip.h"
};I removed the description as it is not given anymore, and now trivial to see what the buffer contain, or i.e. memcmp them if needed.
There was a problem hiding this comment.
Note, all these are examples and suggestions, maybe you wished a different naming/implementation...
|
|
||
| switch (pix_format) { | ||
| case VIDEO_PIX_FMT_JPEG: | ||
| frame = jpeg_frame_buffers[hflip ? 1U : 0U]; |
There was a problem hiding this comment.
&jpeg_frame_buffers[(hflip ? 1U : 0U) * FRAME_SIZE] if the array is flatten
| return 2; | ||
| } | ||
|
|
||
| static int video_sw_generator_fill_compressed(const struct device *const dev, |
| return -ENOMEM; | ||
| } | ||
|
|
||
| /* Copy the JPEG frame data to the video buffer */ |
There was a problem hiding this comment.
/* Copy the compressed frame data to the video buffer */
| #define VIDEO_SW_GENERATOR_FORMAT_CAP_COMPRESSED(pixfmt) \ | ||
| { \ | ||
| .pixelformat = pixfmt, \ | ||
| .width_min = DEFAULT_FRAME_WIDTH, \ | ||
| .width_max = DEFAULT_FRAME_WIDTH, \ | ||
| .height_min = DEFAULT_FRAME_HEIGHT, \ | ||
| .height_max = DEFAULT_FRAME_HEIGHT, \ | ||
| .width_step = 1, \ | ||
| .height_step = 1, \ | ||
| } |
There was a problem hiding this comment.
I think this can be merged with VIDEO_SW_GENERATOR_FORMAT_CAP by:
.width_min = ((pixfmt) == VIDEO_PIX_FMT_JPEG || (pixfmt) == VIDEO_PIX_FMT_PNG) ? DEFAULT_FRAME_WIDTH : 64,
There was a problem hiding this comment.
Maybe this will need some glue to extract the JPEG frame width from the image, maybe some Kconfig like this to keep it simple:
CONFIG_VIDEO_SW_GENERATOR_JPEG_WIDTHCONFIG_VIDEO_SW_GENERATOR_JPEG_HEIGHTCONFIG_VIDEO_SW_GENERATOR_PNG_WIDTHCONFIG_VIDEO_SW_GENERATOR_PNG_HEIGHT
This could be further extended with:
CONFIG_VIDEO_SW_GENERATOR_JPEG_FILECONFIG_VIDEO_SW_GENERATOR_JPEG_HFILP_FILECONFIG_VIDEO_SW_GENERATOR_PNG_FILECONFIG_VIDEO_SW_GENERATOR_PNG_HFLIP_FILE
Which default to the two files stored in-tree and their dimension, so that the user can specify an out-of-tree file if they want.
There was a problem hiding this comment.
Let me know if you wish some example Kconfig/CMakeLists.txt for it, I'd be glad to propose it if you appreciate this change.
| /* Special handling for JPEG and PNG formats */ | ||
| if (fmt->pixelformat == VIDEO_PIX_FMT_JPEG) { | ||
| if (fmt->width != DEFAULT_FRAME_WIDTH || fmt->height != DEFAULT_FRAME_HEIGHT) { | ||
| LOG_ERR("JPEG format only supports %dx%d resolution", DEFAULT_FRAME_WIDTH, | ||
| DEFAULT_FRAME_HEIGHT); | ||
| return -EINVAL; | ||
| } | ||
| fmt->pitch = 0; /* JPEG doesn't have a pitch */ | ||
| fmt->size = JPEG_FRAME_SIZE; | ||
| } else if (fmt->pixelformat == VIDEO_PIX_FMT_PNG) { | ||
| if (fmt->width != DEFAULT_FRAME_WIDTH || fmt->height != DEFAULT_FRAME_HEIGHT) { | ||
| LOG_ERR("PNG format only supports %dx%d resolution", DEFAULT_FRAME_WIDTH, | ||
| DEFAULT_FRAME_HEIGHT); | ||
| return -EINVAL; | ||
| } | ||
| fmt->pitch = 0; /* PNG doesn't have a pitch */ | ||
| fmt->size = PNG_FRAME_SIZE; | ||
| } else { | ||
| ret = video_estimate_fmt_size(fmt); | ||
| if (ret < 0) { | ||
| return ret; | ||
| } |
There was a problem hiding this comment.
All of these code can be drop, just add two entries for JPEG and PNG in video_estimate_fmt_size() and it will be OK. If the estimation is a bit large, you can add to the end:
if (fmt->pixelformat == VIDEO_PIX_FMT_JPEG) {
fmt->size = JPEG_FRAME_SIZE;
}
There was a problem hiding this comment.
I think after applying your own comment, it would become something like fmt->size = MAX(sizeof(jpeg_frame_1_buffer), sizeof(jpeg_frame_2_buffer))?
| if (fmt->pixelformat == VIDEO_PIX_FMT_JPEG) { | ||
| if (fmt->width != DEFAULT_FRAME_WIDTH || fmt->height != DEFAULT_FRAME_HEIGHT) { | ||
| LOG_ERR("JPEG format only supports %dx%d resolution", DEFAULT_FRAME_WIDTH, | ||
| DEFAULT_FRAME_HEIGHT); | ||
| return -EINVAL; | ||
| } |
There was a problem hiding this comment.
No need because video_format_caps_index() already did the job.
| }; | ||
|
|
||
| /* Static JPEG frame buffers. 2 320x160 color-bar pattern frames, h-flipped. */ | ||
| static uint8_t jpeg_frame_buffers[2U][JPEG_FRAME_SIZE] = { |
There was a problem hiding this comment.
I would even go with one frame per variable, making it very trivial and flexible: compressed formats typically give buffers of each different size.
| 0xE0, 0x3B, 0x80, 0xF4, 0x74, 0x00, 0x02, 0x62, 0xA7, 0x26, 0x2C, 0xAD, 0x3C, 0xED, 0xBB, | ||
| 0x7C, 0x96, 0xE1, 0xB5, 0xE3, 0xA0, 0x0C, 0xAA, 0xB6, 0x00, 0x08, 0x10, 0x0E, 0xE3, 0x38, | ||
| 0xE8, 0x00, 0x02, 0x64, 0xA6, 0xA6, 0x4C, 0xAB, 0x3C, 0xED, 0xBB, 0x7C, 0x97, 0x21, 0x95, | ||
| 0xE3, 0xA0, 0x0C, 0xAC, 0xB8, 0x00, 0x0F, 0xFF, 0xD9}, |
There was a problem hiding this comment.
This is a relatively rare syntax for multi-line buffers in Zephyr, with the } on the same line as the rest.
I suppose clang-format did this. You can add a trailing , before the } to indicate clang-format to use the more frequent style of having } on its own line.
| }; | ||
|
|
||
| /* Static JPEG frame buffers. 2 320x160 color-bar pattern frames, h-flipped. */ | ||
| static uint8_t jpeg_frame_buffers[2U][JPEG_FRAME_SIZE] = { |
There was a problem hiding this comment.
Or even better: I would recommend to not store the buffer directly as hex in the source as it is a lot more difficult to inspect the image this way, while .jpeg and .png file are easy to inspect and modify.
How about using this for generating the buffers?
xxd -i frame.jpeg >jpeg_frame.hxxd -i frame_hflip.jpeg >jpeg_frame_hflip.h
See #101883 (comment) for an example of CMakeLists.txt integration. I can provide and test an example CMakeLists.txt snippet if that helps.
You can then include it from the middle of an array for instance.
/* Static JPEG frame buffers. */
static uint8_t jpeg_frame_buffer[] = {
#include "jpeg_frame.h"
};
static uint8_t jpeg_frame_hflip_buffer[] = {
#include "jpeg_frame_hflip.h"
};
/* Static PNG frame buffers. */
static uint8_t png_frame_buffer[] = {
#include "png_frame.h"
};
static uint8_t png_frame_hflip_buffer[] = {
#include "png_frame_hflip.h"
};I removed the description as it is not given anymore, and now trivial to see what the buffer contain, or i.e. memcmp them if needed.
| /* Special handling for JPEG and PNG formats */ | ||
| if (fmt->pixelformat == VIDEO_PIX_FMT_JPEG) { | ||
| if (fmt->width != DEFAULT_FRAME_WIDTH || fmt->height != DEFAULT_FRAME_HEIGHT) { | ||
| LOG_ERR("JPEG format only supports %dx%d resolution", DEFAULT_FRAME_WIDTH, | ||
| DEFAULT_FRAME_HEIGHT); | ||
| return -EINVAL; | ||
| } | ||
| fmt->pitch = 0; /* JPEG doesn't have a pitch */ | ||
| fmt->size = JPEG_FRAME_SIZE; | ||
| } else if (fmt->pixelformat == VIDEO_PIX_FMT_PNG) { | ||
| if (fmt->width != DEFAULT_FRAME_WIDTH || fmt->height != DEFAULT_FRAME_HEIGHT) { | ||
| LOG_ERR("PNG format only supports %dx%d resolution", DEFAULT_FRAME_WIDTH, | ||
| DEFAULT_FRAME_HEIGHT); | ||
| return -EINVAL; | ||
| } | ||
| fmt->pitch = 0; /* PNG doesn't have a pitch */ | ||
| fmt->size = PNG_FRAME_SIZE; | ||
| } else { | ||
| ret = video_estimate_fmt_size(fmt); | ||
| if (ret < 0) { | ||
| return ret; | ||
| } |
There was a problem hiding this comment.
I think after applying your own comment, it would become something like fmt->size = MAX(sizeof(jpeg_frame_1_buffer), sizeof(jpeg_frame_2_buffer))?
| #define VIDEO_SW_GENERATOR_FORMAT_CAP_COMPRESSED(pixfmt) \ | ||
| { \ | ||
| .pixelformat = pixfmt, \ | ||
| .width_min = DEFAULT_FRAME_WIDTH, \ | ||
| .width_max = DEFAULT_FRAME_WIDTH, \ | ||
| .height_min = DEFAULT_FRAME_HEIGHT, \ | ||
| .height_max = DEFAULT_FRAME_HEIGHT, \ | ||
| .width_step = 1, \ | ||
| .height_step = 1, \ | ||
| } |
There was a problem hiding this comment.
Maybe this will need some glue to extract the JPEG frame width from the image, maybe some Kconfig like this to keep it simple:
CONFIG_VIDEO_SW_GENERATOR_JPEG_WIDTHCONFIG_VIDEO_SW_GENERATOR_JPEG_HEIGHTCONFIG_VIDEO_SW_GENERATOR_PNG_WIDTHCONFIG_VIDEO_SW_GENERATOR_PNG_HEIGHT
This could be further extended with:
CONFIG_VIDEO_SW_GENERATOR_JPEG_FILECONFIG_VIDEO_SW_GENERATOR_JPEG_HFILP_FILECONFIG_VIDEO_SW_GENERATOR_PNG_FILECONFIG_VIDEO_SW_GENERATOR_PNG_HFLIP_FILE
Which default to the two files stored in-tree and their dimension, so that the user can specify an out-of-tree file if they want.
| #define VIDEO_SW_GENERATOR_FORMAT_CAP_COMPRESSED(pixfmt) \ | ||
| { \ | ||
| .pixelformat = pixfmt, \ | ||
| .width_min = DEFAULT_FRAME_WIDTH, \ | ||
| .width_max = DEFAULT_FRAME_WIDTH, \ | ||
| .height_min = DEFAULT_FRAME_HEIGHT, \ | ||
| .height_max = DEFAULT_FRAME_HEIGHT, \ | ||
| .width_step = 1, \ | ||
| .height_step = 1, \ | ||
| } |
There was a problem hiding this comment.
Let me know if you wish some example Kconfig/CMakeLists.txt for it, I'd be glad to propose it if you appreciate this change.
| }; | ||
|
|
||
| /* Static JPEG frame buffers. 2 320x160 color-bar pattern frames, h-flipped. */ | ||
| static uint8_t jpeg_frame_buffers[2U][JPEG_FRAME_SIZE] = { |
There was a problem hiding this comment.
Note, all these are examples and suggestions, maybe you wished a different naming/implementation...
josuah
left a comment
There was a problem hiding this comment.
My bad, I meant to comment, not to approve yet...
| #define MCUX_JPEGDEC_HEIGHT_MIN 64U | ||
| #define MCUX_JPEGDEC_HEIGHT_MAX 0x2000U |
There was a problem hiding this comment.
height is same as width so these can be drop as you drop already the height_step
| if (caps->type == VIDEO_BUF_TYPE_OUTPUT) { | ||
| if (data->first_frame_rcv) { | ||
| /* Assign the format capability based on parsed JPEG header. */ | ||
| mcux_jpegdec_out_fmts[0] = mcux_jpegdec_out_all_fmts[data->format_idx]; | ||
| } else { | ||
| LOG_WRN("Output format can only be obtained after first input buffer " | ||
| "is received, use VIDEO_PIX_FMT_NV12 as default since it is mostly " | ||
| "used by JPEG."); | ||
| mcux_jpegdec_out_fmts[0] = mcux_jpegdec_out_all_fmts[0]; |
There was a problem hiding this comment.
caps is the capability of the HW so here we should expose all output formats that the HW / driver can support. It does not depend on the first frame header.



Uh oh!
There was an error while loading. Please reload this page.