From b35e569f3657ed78886e0c289e90ac309fd0d6dd Mon Sep 17 00:00:00 2001 From: xwx <354146137@qq.com> Date: Thu, 26 Feb 2026 16:05:51 +0800 Subject: [PATCH 1/4] feat: enable Raspberry Pi to support V4L2 --- .../ai_video/src/ai_video_input.c | 11 +- boards/LINUX/Raspberry_Pi/Kconfig | 15 ++ boards/LINUX/Raspberry_Pi/tdd_camera_v4l2.c | 228 ++++++++++++++++++ boards/LINUX/Raspberry_Pi/tdd_camera_v4l2.h | 27 +++ boards/LINUX/TKL_Kconfig | 12 +- platform/platform_config.yaml | 2 +- 6 files changed, 285 insertions(+), 10 deletions(-) create mode 100644 boards/LINUX/Raspberry_Pi/tdd_camera_v4l2.c create mode 100644 boards/LINUX/Raspberry_Pi/tdd_camera_v4l2.h diff --git a/apps/tuya.ai/ai_components/ai_video/src/ai_video_input.c b/apps/tuya.ai/ai_components/ai_video/src/ai_video_input.c index f710a12fb..9de12ec75 100644 --- a/apps/tuya.ai/ai_components/ai_video/src/ai_video_input.c +++ b/apps/tuya.ai/ai_components/ai_video/src/ai_video_input.c @@ -158,8 +158,17 @@ OPERATE_RET ai_video_init(AI_VIDEO_CFG_T *vi_cfg) sg_camera_cfg.get_frame_cb = __get_raw_frame_cb; sg_camera_cfg.get_encoded_frame_cb = __get_jpeg_frame_cb; - /* Set JPEG encoded */ + /* + * - DVP camera driver supports BOTH raw + encoded. + * - Current Linux V4L2(UVC) camera TDD supports only one output per open. + * For Raspberry Pi V4L2 use-case, MCP tool needs JPEG most. + */ +#if defined(ENABLE_CAMERA_V4L2) && (ENABLE_CAMERA_V4L2 == 1) + sg_camera_cfg.out_fmt = TDL_CAMERA_FMT_JPEG; + sg_camera_cfg.get_frame_cb = NULL; +#else sg_camera_cfg.out_fmt = TDL_CAMERA_FMT_JPEG_YUV422_BOTH; +#endif #if defined(ENABLE_COMP_AI_VIDEO_JPEG_QUALITY) && (ENABLE_COMP_AI_VIDEO_JPEG_QUALITY == 1) sg_camera_cfg.encoded_quality.jpeg_cfg.enable = 1; diff --git a/boards/LINUX/Raspberry_Pi/Kconfig b/boards/LINUX/Raspberry_Pi/Kconfig index 4b3ca4154..49b36781e 100644 --- a/boards/LINUX/Raspberry_Pi/Kconfig +++ b/boards/LINUX/Raspberry_Pi/Kconfig @@ -51,4 +51,19 @@ menu "Raspberry Pi Board Configuration" default "s" ---help--- Specify the keyboard button device value for input handling. + + config ENABLE_CAMERA + bool "Enable Camera" + default n + + if (ENABLE_CAMERA) + config ENABLE_CAMERA_V4L2 + bool "Enable V4L2 USB camera" + default y + + config CAMERA_V4L2_DEVNODE + string "V4L2 device node" + depends on ENABLE_CAMERA_V4L2 + default "/dev/video0" + endif endmenu diff --git a/boards/LINUX/Raspberry_Pi/tdd_camera_v4l2.c b/boards/LINUX/Raspberry_Pi/tdd_camera_v4l2.c new file mode 100644 index 000000000..b88911d7e --- /dev/null +++ b/boards/LINUX/Raspberry_Pi/tdd_camera_v4l2.c @@ -0,0 +1,228 @@ +/** + * @file tdd_camera_v4l2.c + * @brief V4L2 (UVC) camera driver implementation for Linux + * + * This file implements the V4L2 camera driver adapter on Linux, including: + * - V4L2 device open/start/stop/close via TKL layer + * - Frame capture thread (dequeue/queue) + * - Frame buffer copy and posting to TDL camera manager + * - Camera device registration for upper-layer discovery + * + * @copyright Copyright (c) 2021-2025 Tuya Inc. All Rights Reserved. + * + */ +#include "tdd_camera_v4l2.h" + +#include "tal_api.h" +#include "tuya_error_code.h" + +#include "tdl_camera_driver.h" + +#include "camera/tkl_camera_v4l2.h" + +#define V4L2_DEVNODE_MAX_LEN 128 + +typedef struct { + char devnode[V4L2_DEVNODE_MAX_LEN]; + + TKL_CAMERA_V4L2_HANDLE_T tkl_hdl; + + THREAD_HANDLE thread; + volatile bool running; + + uint16_t width; + uint16_t height; + uint16_t fps; + + TUYA_FRAME_FMT_E post_fmt; + uint32_t frame_id; +} CAMERA_V4L2_DEV_T; + +static void __camera_v4l2_capture_task(void *args) +{ + CAMERA_V4L2_DEV_T *dev = (CAMERA_V4L2_DEV_T *)args; + if (!dev) { + return; + } + + while (dev->running) { + uint8_t *v4l2_data = NULL; + uint32_t v4l2_len = 0; + uint32_t v4l2_index = 0; + + OPERATE_RET rt = tkl_camera_v4l2_dequeue(dev->tkl_hdl, &v4l2_data, &v4l2_len, &v4l2_index); + if (rt != OPRT_OK) { + tal_system_sleep(10); + continue; + } + + TDD_CAMERA_FRAME_T *tdd_frame = tdl_camera_create_tdd_frame((TDD_CAMERA_DEV_HANDLE_T)dev, dev->post_fmt); + if (tdd_frame == NULL) { + (void)tkl_camera_v4l2_queue(dev->tkl_hdl, v4l2_index); + tal_system_sleep(1); + continue; + } + + if (v4l2_len > tdd_frame->frame.data_len) { + static bool warned = false; + if (!warned) { + warned = true; + PR_WARN("v4l2 frame too large: %u > %u, drop", v4l2_len, tdd_frame->frame.data_len); + } + tdl_camera_release_tdd_frame((TDD_CAMERA_DEV_HANDLE_T)dev, tdd_frame); + (void)tkl_camera_v4l2_queue(dev->tkl_hdl, v4l2_index); + continue; + } + + memcpy(tdd_frame->frame.data, v4l2_data, v4l2_len); + + tdd_frame->frame.id = (uint16_t)(dev->frame_id++); + tdd_frame->frame.is_i_frame = 1; + tdd_frame->frame.is_complete = 1; + tdd_frame->frame.width = dev->width; + tdd_frame->frame.height = dev->height; + tdd_frame->frame.data_len = v4l2_len; + tdd_frame->frame.total_frame_len = v4l2_len; + + rt = tdl_camera_post_tdd_frame((TDD_CAMERA_DEV_HANDLE_T)dev, tdd_frame); + if (rt != OPRT_OK) { + tdl_camera_release_tdd_frame((TDD_CAMERA_DEV_HANDLE_T)dev, tdd_frame); + } + + (void)tkl_camera_v4l2_queue(dev->tkl_hdl, v4l2_index); + } +} + +static OPERATE_RET __tdd_camera_v4l2_open(TDD_CAMERA_DEV_HANDLE_T device, TDD_CAMERA_OPEN_CFG_T *cfg) +{ + CAMERA_V4L2_DEV_T *dev = (CAMERA_V4L2_DEV_T *)device; + if (dev == NULL || cfg == NULL) { + return OPRT_INVALID_PARM; + } + + if (dev->running) { + return OPRT_OK; + } + + bool need_raw = (cfg->out_fmt & TDL_IMG_FMT_RAW_MASK) ? true : false; + bool need_encoded = (cfg->out_fmt & TDL_IMG_FMT_ENCODED_MASK) ? true : false; + + if (need_raw && need_encoded) { + PR_ERR("V4L2 camera does not support BOTH raw+encoded in one open yet"); + return OPRT_NOT_SUPPORTED; + } + + if (cfg->out_fmt == TDL_CAMERA_FMT_H264 || cfg->out_fmt == TDL_CAMERA_FMT_H264_YUV422_BOTH) { + PR_ERR("V4L2 camera H264 output not supported"); + return OPRT_NOT_SUPPORTED; + } + + dev->width = cfg->width; + dev->height = cfg->height; + dev->fps = cfg->fps; + + TKL_CAMERA_V4L2_CFG_T v4l2_cfg = {0}; + v4l2_cfg.devnode = dev->devnode; + v4l2_cfg.width = cfg->width; + v4l2_cfg.height = cfg->height; + v4l2_cfg.fps = cfg->fps; + v4l2_cfg.buffer_count = 4; + + if (need_encoded || cfg->out_fmt == TDL_CAMERA_FMT_JPEG) { + v4l2_cfg.pixfmt = TKL_CAMERA_V4L2_PIXFMT_MJPEG; + dev->post_fmt = TUYA_FRAME_FMT_JPEG; + } else { + v4l2_cfg.pixfmt = TKL_CAMERA_V4L2_PIXFMT_YUYV; + dev->post_fmt = TUYA_FRAME_FMT_YUV422; + } + + OPERATE_RET rt = tkl_camera_v4l2_open(&dev->tkl_hdl, &v4l2_cfg); + if (rt != OPRT_OK) { + PR_ERR("tkl_camera_v4l2_open failed: %d", rt); + return rt; + } + + rt = tkl_camera_v4l2_start(dev->tkl_hdl); + if (rt != OPRT_OK) { + PR_ERR("tkl_camera_v4l2_start failed: %d", rt); + (void)tkl_camera_v4l2_close(dev->tkl_hdl); + dev->tkl_hdl = NULL; + return rt; + } + + dev->running = true; + THREAD_CFG_T thread_cfg = {8192, THREAD_PRIO_1, "v4l2_cam"}; + rt = tal_thread_create_and_start(&dev->thread, NULL, NULL, __camera_v4l2_capture_task, dev, &thread_cfg); + if (rt != OPRT_OK) { + dev->running = false; + (void)tkl_camera_v4l2_stop(dev->tkl_hdl); + (void)tkl_camera_v4l2_close(dev->tkl_hdl); + dev->tkl_hdl = NULL; + return rt; + } + + return OPRT_OK; +} + +static OPERATE_RET __tdd_camera_v4l2_close(TDD_CAMERA_DEV_HANDLE_T device) +{ + CAMERA_V4L2_DEV_T *dev = (CAMERA_V4L2_DEV_T *)device; + if (dev == NULL) { + return OPRT_INVALID_PARM; + } + + if (!dev->running) { + return OPRT_OK; + } + + dev->running = false; + + if (dev->thread) { + tal_thread_delete(dev->thread); + dev->thread = NULL; + } + + if (dev->tkl_hdl) { + (void)tkl_camera_v4l2_stop(dev->tkl_hdl); + (void)tkl_camera_v4l2_close(dev->tkl_hdl); + dev->tkl_hdl = NULL; + } + + return OPRT_OK; +} + +OPERATE_RET tdd_camera_v4l2_register(const char *name, const char *devnode) +{ + if (name == NULL || devnode == NULL) { + return OPRT_INVALID_PARM; + } + + CAMERA_V4L2_DEV_T *dev = (CAMERA_V4L2_DEV_T *)tal_malloc(sizeof(CAMERA_V4L2_DEV_T)); + if (dev == NULL) { + return OPRT_MALLOC_FAILED; + } + memset(dev, 0, sizeof(*dev)); + + strncpy(dev->devnode, devnode, sizeof(dev->devnode) - 1); + + TDD_CAMERA_DEV_INFO_T dev_info = {0}; + dev_info.type = TDL_CAMERA_DVP; /* Keep existing type for compatibility with apps expecting a camera device */ + dev_info.max_fps = 60; + dev_info.max_width = 1920; + dev_info.max_height = 1080; + dev_info.fmt = TUYA_FRAME_FMT_YUV422; + + TDD_CAMERA_INTFS_T intfs = { + .open = __tdd_camera_v4l2_open, + .close = __tdd_camera_v4l2_close, + }; + + OPERATE_RET rt = tdl_camera_device_register((char *)name, (TDD_CAMERA_DEV_HANDLE_T)dev, &intfs, &dev_info); + if (rt != OPRT_OK) { + tal_free(dev); + return rt; + } + + PR_INFO("registered V4L2 camera: name=%s devnode=%s", name, devnode); + return OPRT_OK; +} diff --git a/boards/LINUX/Raspberry_Pi/tdd_camera_v4l2.h b/boards/LINUX/Raspberry_Pi/tdd_camera_v4l2.h new file mode 100644 index 000000000..d83d1be36 --- /dev/null +++ b/boards/LINUX/Raspberry_Pi/tdd_camera_v4l2.h @@ -0,0 +1,27 @@ +/** + * @file tdd_camera_v4l2.h + * @brief V4L2 (UVC) camera TDD adapter for TDL camera manager. + */ + +#ifndef __TDD_CAMERA_V4L2_H__ +#define __TDD_CAMERA_V4L2_H__ + +#include "tuya_cloud_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Register a Linux V4L2 USB camera as a TDL camera device. + * + * @param name Camera device name (used by tdl_camera_find_dev) + * @param devnode V4L2 device node path, e.g. "/dev/video0" + */ +OPERATE_RET tdd_camera_v4l2_register(const char *name, const char *devnode); + +#ifdef __cplusplus +} +#endif + +#endif /* __TDD_CAMERA_V4L2_H__ */ diff --git a/boards/LINUX/TKL_Kconfig b/boards/LINUX/TKL_Kconfig index c6f8d87fe..333748ef9 100644 --- a/boards/LINUX/TKL_Kconfig +++ b/boards/LINUX/TKL_Kconfig @@ -50,12 +50,12 @@ menu "TKL Board Configuration" default n config PWM_SYSFS_CHIP - int "Linux PWM sysfs chip index (/sys/class/pwm/pwmchipX)" + int "Linux PWM sysfs chip index" depends on ENABLE_PWM default 0 config PWM_SYSFS_CHANNEL_BASE - int "Linux PWM sysfs channel base (pwmN = base + ch_id)" + int "Linux PWM sysfs channel base" depends on ENABLE_PWM default 0 @@ -63,14 +63,10 @@ menu "TKL Board Configuration" bool "Enable UART" default n - config TKL_UART_USE_FAKE - bool "Use fake UART (stdin/UDP) instead of hardware ttyAMA*" + config TKL_UART_REDIRECT_LOG_TO_STDOUT + bool "UART redirection (stdin/stdout/UDP) instead of hardware ttyAMA*" depends on ENABLE_UART default y - ---help--- - Select Y to keep the existing fake UART implementation that uses - stdin for RX and UDP for TX/RX. Select N to use the Raspberry Pi - hardware UART devices (ttyAMA*). config ENABLE_AUDIO bool diff --git a/platform/platform_config.yaml b/platform/platform_config.yaml index 0811aba85..f91025856 100755 --- a/platform/platform_config.yaml +++ b/platform/platform_config.yaml @@ -12,7 +12,7 @@ platforms: - name: LINUX repo: https://github.com/tuya/TuyaOpen-ubuntu branch: platform_ubuntu - commit: 0fb3ab4c531f03e383fcc3b2d20c775ba1817381 + commit: e13836b769c12741df998a34e72d8251c81cd731 - name: T5AI repo: https://github.com/tuya/TuyaOpen-T5AI From 23b180bf9bf1b1618a80684ec4697ce5c5ea4820 Mon Sep 17 00:00:00 2001 From: xwx <354146137@qq.com> Date: Thu, 26 Feb 2026 16:07:21 +0800 Subject: [PATCH 2/4] feat: enable Raspberry Pi to support V4L2 --- boards/LINUX/Raspberry_Pi/board_com_api.c | 39 +++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/boards/LINUX/Raspberry_Pi/board_com_api.c b/boards/LINUX/Raspberry_Pi/board_com_api.c index c44c5d94f..68a828600 100644 --- a/boards/LINUX/Raspberry_Pi/board_com_api.c +++ b/boards/LINUX/Raspberry_Pi/board_com_api.c @@ -20,6 +20,10 @@ #include "tdd_button_keyboard.h" #endif +#if defined(ENABLE_CAMERA_V4L2) && (ENABLE_CAMERA_V4L2 == 1) +#include "tdd_camera_v4l2.h" +#endif + #include "board_com_api.h" /*********************************************************** @@ -173,6 +177,34 @@ static OPERATE_RET __board_register_led(void) return rt; } +/** + * @brief Registers V4L2 USB camera for Raspberry Pi platform + */ +static OPERATE_RET __board_register_camera(void) +{ + OPERATE_RET rt = OPRT_OK; + +#if defined(ENABLE_CAMERA_V4L2) && (ENABLE_CAMERA_V4L2 == 1) + #if defined(CAMERA_NAME) + #if defined(CAMERA_V4L2_DEVNODE) + PR_INFO("Registering V4L2 camera: %s (%s)", CAMERA_NAME, CAMERA_V4L2_DEVNODE); + rt = tdd_camera_v4l2_register(CAMERA_NAME, CAMERA_V4L2_DEVNODE); + #else + PR_INFO("Registering V4L2 camera: %s (/dev/video0)", CAMERA_NAME); + rt = tdd_camera_v4l2_register(CAMERA_NAME, "/dev/video0"); + #endif + if (OPRT_OK != rt) { + PR_WARN("Failed to register V4L2 camera: %d", rt); + return rt; + } + #else + PR_WARN("CAMERA_NAME not defined, skipping camera registration"); + #endif +#endif + + return rt; +} + /** * @brief Registers all the hardware peripherals on the Raspberry Pi platform. * @@ -180,6 +212,7 @@ static OPERATE_RET __board_register_led(void) * - ALSA audio device (if ENABLE_AUDIO_ALSA is enabled) * - Button * - LED + * - Camera (if ENABLE_CAMERA_V4L2 is enabled) * * @return Returns OPRT_OK on success, or an appropriate error code on failure. */ @@ -208,6 +241,12 @@ OPERATE_RET board_register_hardware(void) PR_WARN("LED registration failed: %d", rt); } + // Register camera (V4L2) + rt = __board_register_camera(); + if (OPRT_OK != rt) { + PR_WARN("Camera registration failed: %d", rt); + } + PR_INFO("Raspberry Pi platform hardware registration completed"); return OPRT_OK; From 22f37bff2f14607f6bf9a9ce4e43ade0c1adc2a2 Mon Sep 17 00:00:00 2001 From: xwx <354146137@qq.com> Date: Thu, 26 Feb 2026 17:21:16 +0800 Subject: [PATCH 3/4] fix some bugs --- src/peripherals/camera/tdl_camera/src/tdl_camera_manage.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/peripherals/camera/tdl_camera/src/tdl_camera_manage.c b/src/peripherals/camera/tdl_camera/src/tdl_camera_manage.c index 696b8a692..bfad3e77d 100644 --- a/src/peripherals/camera/tdl_camera/src/tdl_camera_manage.c +++ b/src/peripherals/camera/tdl_camera/src/tdl_camera_manage.c @@ -14,7 +14,11 @@ #include "tuya_cloud_types.h" #include "tuya_list.h" #include "tal_api.h" + +#if defined(CONFIG_ENABLE_DVP) && (CONFIG_ENABLE_DVP==1) #include "tkl_dvp.h" +#endif + #include "tkl_memory.h" #include "tdl_camera_manage.h" From 546770c3a40c7f068e9852c477e15222e00c0d24 Mon Sep 17 00:00:00 2001 From: xwx <354146137@qq.com> Date: Thu, 26 Feb 2026 18:14:36 +0800 Subject: [PATCH 4/4] fix some bugs --- boards/LINUX/Raspberry_Pi/CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/boards/LINUX/Raspberry_Pi/CMakeLists.txt b/boards/LINUX/Raspberry_Pi/CMakeLists.txt index 7f2e485a9..8483545a6 100644 --- a/boards/LINUX/Raspberry_Pi/CMakeLists.txt +++ b/boards/LINUX/Raspberry_Pi/CMakeLists.txt @@ -16,6 +16,13 @@ set(MODULE_NAME "Raspberry_Pi") # ) file(GLOB_RECURSE LIB_SRCS "${MODULE_PATH}/*.c") +# Enable V4L2 camera driver if camera support is enabled +if (CONFIG_ENABLE_CAMERA STREQUAL "y" AND CONFIG_ENABLE_CAMERA_V4L2 STREQUAL "y") + message(STATUS "Raspberry Pi board: Enabling V4L2 camera driver") +else() + list(REMOVE_ITEM LIB_SRCS "${MODULE_PATH}/tdd_camera_v4l2.c") +endif() + # LIB_PUBLIC_INC set(LIB_PUBLIC_INC ${MODULE_PATH})