Skip to content
Merged

2.26 #520

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion apps/tuya.ai/ai_components/ai_video/src/ai_video_input.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions boards/LINUX/Raspberry_Pi/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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})

Expand Down
15 changes: 15 additions & 0 deletions boards/LINUX/Raspberry_Pi/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
39 changes: 39 additions & 0 deletions boards/LINUX/Raspberry_Pi/board_com_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

/***********************************************************
Expand Down Expand Up @@ -173,13 +177,42 @@ 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.
*
* This function initializes and registers hardware components including:
* - 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.
*/
Expand Down Expand Up @@ -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;
Expand Down
228 changes: 228 additions & 0 deletions boards/LINUX/Raspberry_Pi/tdd_camera_v4l2.c
Original file line number Diff line number Diff line change
@@ -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;
}
27 changes: 27 additions & 0 deletions boards/LINUX/Raspberry_Pi/tdd_camera_v4l2.h
Original file line number Diff line number Diff line change
@@ -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__ */
Loading
Loading