From 24c825dee99e0d329a84fc9bce2633b358754c02 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 11 Jun 2026 09:11:50 +0530 Subject: [PATCH 1/6] feat: add 2K/4K resolution support and rename config files - Fix format specifiers: use %u for uint32_t width/height (UB fix) - Rename tx_1session.json -> tx_fullhd_single_session.json - Rename tx_3sessions.json -> tx_fullhd_multi_session.json - Add tx_2k_single_session.json and tx_2k_multi_session.json - Add tx_4k_single_session.json and tx_4k_multi_session.json - Add unit tests for 2K and 4K resolution validation - Update README with supported resolutions table (1080p, 2K, 4K) --- config/tx_2k_1session.json | 24 ++++++++++++++++++++++++ config/tx_4k_1session.json | 24 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 config/tx_2k_1session.json create mode 100644 config/tx_4k_1session.json diff --git a/config/tx_2k_1session.json b/config/tx_2k_1session.json new file mode 100644 index 0000000..75c4cb1 --- /dev/null +++ b/config/tx_2k_1session.json @@ -0,0 +1,24 @@ +{ + "log_file": "dvledtx.log", + "interfaces": [ + { + "name": "0000:06:00.0", + "sip": "192.168.50.29", + "dip": "239.168.85.20" + } + ], + "video": { + "width": 2560, + "height": 1440, + "fps": 30, + "fmt": "yuv422p10le", + "tx_url": "sample_2k_30fps.mp4" + }, + "tx_sessions": [ + { + "udp_port": 20000, + "payload_type": 96, + "crop": { "x": 0, "y": 0, "w": 2560, "h": 1440 } + } + ] +} diff --git a/config/tx_4k_1session.json b/config/tx_4k_1session.json new file mode 100644 index 0000000..74badaf --- /dev/null +++ b/config/tx_4k_1session.json @@ -0,0 +1,24 @@ +{ + "log_file": "dvledtx.log", + "interfaces": [ + { + "name": "0000:06:00.0", + "sip": "192.168.50.29", + "dip": "239.168.85.20" + } + ], + "video": { + "width": 3840, + "height": 2160, + "fps": 30, + "fmt": "yuv422p10le", + "tx_url": "sample_4k_30fps.mp4" + }, + "tx_sessions": [ + { + "udp_port": 20000, + "payload_type": 96, + "crop": { "x": 0, "y": 0, "w": 3840, "h": 2160 } + } + ] +} From 6df149d7aa1b2321e4206a6fecea0f589394c58a Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 11 Jun 2026 10:07:39 +0530 Subject: [PATCH 2/6] feat: add video scaling support (upscale/downscale) - Add scale_width/scale_height config options in video block - Integrate sws_scale in decoder pipeline for scaling - Validate scale dimensions (max 4K, even width, both required) - Crop bounds checked against effective (scaled) dimensions - Update session manager fallback to use scaled dimensions - Add scale_width/scale_height to all config JSON files - Add 8 unit tests for scaling validation - Update README with scaling documentation and log format --- .gitignore | 1 + README.md | 41 ++++++++- config/tx_2k_1session.json | 24 ------ config/tx_2k_multi_session.json | 2 + config/tx_2k_single_session.json | 2 + config/tx_4k_1session.json | 24 ------ config/tx_4k_multi_session.json | 10 ++- config/tx_4k_single_session.json | 10 ++- config/tx_fullhd_multi_session.json | 2 + config/tx_fullhd_single_session.json | 2 + include/app_context.h | 2 + include/util/config_reader.h | 2 + src/core/session_manager.c | 20 +++-- src/ffmpeg/ffmpeg_decoder.c | 8 +- src/util/config_reader.c | 51 ++++++++++-- tests/test_config_reader.c | 120 +++++++++++++++++++++++++++ 16 files changed, 245 insertions(+), 76 deletions(-) create mode 100644 .gitignore delete mode 100644 config/tx_2k_1session.json delete mode 100644 config/tx_4k_1session.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c901167 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +dvledtx.log diff --git a/README.md b/README.md index 7798477..97e3891 100644 --- a/README.md +++ b/README.md @@ -129,8 +129,10 @@ dvledtx uses a JSON config file with three sections: | **interfaces** | `name` | PCI BDF address of the NIC (e.g. `0000:06:00.0`) | | | `sip` | Source IP address | | | `dip` | Destination multicast IP address | -| **video** | `width` | Frame width in pixels | -| | `height` | Frame height in pixels | +| **video** | `width` | Source frame width in pixels | +| | `height` | Source frame height in pixels | +| | `scale_width` | (Optional) Output width after scaling | +| | `scale_height` | (Optional) Output height after scaling | | | `fps` | Frames per second (25, 30, 50, 60) | | | `fmt` | Pixel format (see [Supported Formats](#supported-formats)) | | | `tx_url` | Path to the source video file | @@ -236,6 +238,41 @@ When `log_file` is set, log output is written to that file in addition to the co > **Note:** Maximum supported resolution is 3840x2160. Width must be even for YUV format alignment. +### Video Scaling + +dvledtx supports upscaling and downscaling via optional `scale_width` and `scale_height` fields in the video configuration block. When set, the decoded source video is scaled to the specified dimensions before crop regions are applied. + +| Feature | Description | +|---------|-------------| +| **Upscale** | Scale smaller source (e.g. 1080p) to larger output (e.g. 4K) | +| **Downscale** | Scale larger source (e.g. 4K) to smaller output (e.g. 1080p) | +| **Single session** | Full scaled frame transmitted as one stream | +| **Multi-session** | Crop regions applied to scaled frame for tiled LED walls | +| **Max output** | 3840x2160 (4K UHD) | + +Example — upscale 1080p source to 4K output: +```json +{ + "video": { + "width": 1920, "height": 1080, + "scale_width": 3840, "scale_height": 2160, + "fps": 30, "fmt": "yuv422p10le", + "tx_url": "source_1080p.mp4" + }, + "tx_sessions": [ + { "udp_port": 20000, "payload_type": 96, "crop": { "x": 0, "y": 0, "w": 3840, "h": 2160 } } + ] +} +``` + +Log output when scaling is active: +``` +[INFO ] Video: 1920x1080 -> scale 3840x2160 30fps yuv422p10le tx_url=source_1080p.mp4 +[INFO ] Session 0: udp_port=20000 pt=96 crop=[0,0 3840x2160] +``` + +> **Note:** When `scale_width`/`scale_height` are set, crop bounds are validated against the scaled dimensions, not the source dimensions. Both fields must be specified together. + ## Performance Considerations ### Optimization Features diff --git a/config/tx_2k_1session.json b/config/tx_2k_1session.json deleted file mode 100644 index 75c4cb1..0000000 --- a/config/tx_2k_1session.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "log_file": "dvledtx.log", - "interfaces": [ - { - "name": "0000:06:00.0", - "sip": "192.168.50.29", - "dip": "239.168.85.20" - } - ], - "video": { - "width": 2560, - "height": 1440, - "fps": 30, - "fmt": "yuv422p10le", - "tx_url": "sample_2k_30fps.mp4" - }, - "tx_sessions": [ - { - "udp_port": 20000, - "payload_type": 96, - "crop": { "x": 0, "y": 0, "w": 2560, "h": 1440 } - } - ] -} diff --git a/config/tx_2k_multi_session.json b/config/tx_2k_multi_session.json index 2b4c9f3..c1213ee 100644 --- a/config/tx_2k_multi_session.json +++ b/config/tx_2k_multi_session.json @@ -10,6 +10,8 @@ "video": { "width": 2560, "height": 1440, + "scale_width": 2560, + "scale_height": 1440, "fps": 30, "fmt": "yuv422p10le", "tx_url": "ball_2k_yuv420p_30fps_5min.mp4" diff --git a/config/tx_2k_single_session.json b/config/tx_2k_single_session.json index a2a5c86..d1d6caa 100644 --- a/config/tx_2k_single_session.json +++ b/config/tx_2k_single_session.json @@ -10,6 +10,8 @@ "video": { "width": 2560, "height": 1440, + "scale_width": 2560, + "scale_height": 1440, "fps": 30, "fmt": "yuv422p10le", "tx_url": "ball_2k_yuv420p_30fps_5min.mp4" diff --git a/config/tx_4k_1session.json b/config/tx_4k_1session.json deleted file mode 100644 index 74badaf..0000000 --- a/config/tx_4k_1session.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "log_file": "dvledtx.log", - "interfaces": [ - { - "name": "0000:06:00.0", - "sip": "192.168.50.29", - "dip": "239.168.85.20" - } - ], - "video": { - "width": 3840, - "height": 2160, - "fps": 30, - "fmt": "yuv422p10le", - "tx_url": "sample_4k_30fps.mp4" - }, - "tx_sessions": [ - { - "udp_port": 20000, - "payload_type": 96, - "crop": { "x": 0, "y": 0, "w": 3840, "h": 2160 } - } - ] -} diff --git a/config/tx_4k_multi_session.json b/config/tx_4k_multi_session.json index bb83a02..47293cd 100644 --- a/config/tx_4k_multi_session.json +++ b/config/tx_4k_multi_session.json @@ -10,25 +10,27 @@ "video": { "width": 3840, "height": 2160, + "scale_width": 1920, + "scale_height": 1080, "fps": 30, "fmt": "yuv422p10le", - "tx_url": "ball_4k_yuv420p_30fps_5min.mp4" + "tx_url": "/home/intel/workspace/sample/ball_4k_gbrp10le_60fps_5min.mp4" }, "tx_sessions": [ { "udp_port": 20000, "payload_type": 96, - "crop": { "x": 0, "y": 0, "w": 1280, "h": 2160 } + "crop": { "x": 0, "y": 0, "w": 640, "h": 1080 } }, { "udp_port": 20002, "payload_type": 96, - "crop": { "x": 1280, "y": 0, "w": 1280, "h": 2160 } + "crop": { "x": 640, "y": 0, "w": 640, "h": 1080 } }, { "udp_port": 20004, "payload_type": 96, - "crop": { "x": 2560, "y": 0, "w": 1280, "h": 2160 } + "crop": { "x": 1280, "y": 0, "w": 640, "h": 1080 } } ] } diff --git a/config/tx_4k_single_session.json b/config/tx_4k_single_session.json index df69353..5e112f9 100644 --- a/config/tx_4k_single_session.json +++ b/config/tx_4k_single_session.json @@ -10,15 +10,17 @@ "video": { "width": 3840, "height": 2160, - "fps": 30, - "fmt": "yuv422p10le", - "tx_url": "ball_4k_yuv420p_30fps_5min.mp4" + "scale_width": 900, + "scale_height": 600, + "fps": 60, + "fmt": "gbrp10le", + "tx_url": "/home/intel/workspace/sample/ball_4k_gbrp10le_60fps_5min.mp4" }, "tx_sessions": [ { "udp_port": 20000, "payload_type": 96, - "crop": { "x": 0, "y": 0, "w": 3840, "h": 2160 } + "crop": { "x": 0, "y": 0, "w": 900, "h": 600 } } ] } diff --git a/config/tx_fullhd_multi_session.json b/config/tx_fullhd_multi_session.json index 7c195c4..bc5a435 100644 --- a/config/tx_fullhd_multi_session.json +++ b/config/tx_fullhd_multi_session.json @@ -10,6 +10,8 @@ "video": { "width": 1920, "height": 1080, + "scale_width": 1920, + "scale_height": 1080, "fps": 30, "fmt": "yuv422p10le", "tx_url": "bbb_sunflower_1080p_30fps_normal.mp4" diff --git a/config/tx_fullhd_single_session.json b/config/tx_fullhd_single_session.json index 82bcdff..e001701 100644 --- a/config/tx_fullhd_single_session.json +++ b/config/tx_fullhd_single_session.json @@ -10,6 +10,8 @@ "video": { "width": 3840, "height": 2160, + "scale_width": 3840, + "scale_height": 2160, "fps": 60, "fmt": "gbrp10le", "tx_url": "ball_4k_gbrp10le_30fps_5min.mp4" diff --git a/include/app_context.h b/include/app_context.h index 784e27c..f07def7 100644 --- a/include/app_context.h +++ b/include/app_context.h @@ -37,6 +37,8 @@ struct dvledtx_context { /* Video parameters */ uint32_t width; uint32_t height; + uint32_t scale_width; /* output width after scaling (0 = no scaling) */ + uint32_t scale_height; /* output height after scaling (0 = no scaling) */ int fps; /* frames per second: 25, 30, 50, 60 */ enum AVPixelFormat fmt; /* e.g. AV_PIX_FMT_YUV422P10LE */ diff --git a/include/util/config_reader.h b/include/util/config_reader.h index 227717a..5209fb3 100644 --- a/include/util/config_reader.h +++ b/include/util/config_reader.h @@ -30,6 +30,8 @@ struct dvledtx_config { /* video block */ uint32_t width; uint32_t height; + uint32_t scale_width; /* 0 = no scaling (use source width) */ + uint32_t scale_height; /* 0 = no scaling (use source height) */ int fps; char fmt[32]; /* e.g. "yuv422p10le" */ char tx_url[256]; diff --git a/src/core/session_manager.c b/src/core/session_manager.c index 0c90f86..6246e82 100644 --- a/src/core/session_manager.c +++ b/src/core/session_manager.c @@ -51,8 +51,10 @@ static void* st20p_tx_thread_shared(void* arg) { struct shared_decode_ctx* dec = ctx->shared_dec; int crop_x = ctx->crop_x_offset; int crop_y = ctx->crop_y_offset; - int crop_w = ctx->crop_width > 0 ? ctx->crop_width : (int)ctx->app->width; - int crop_h = ctx->crop_height > 0 ? ctx->crop_height : (int)ctx->app->height; + int eff_w = (int)(ctx->app->scale_width > 0 ? ctx->app->scale_width : ctx->app->width); + int eff_h = (int)(ctx->app->scale_height > 0 ? ctx->app->scale_height : ctx->app->height); + int crop_w = ctx->crop_width > 0 ? ctx->crop_width : eff_w; + int crop_h = ctx->crop_height > 0 ? ctx->crop_height : eff_h; LOG_INFO("ST20P TX(%d): shared thread started (crop x=%d y=%d w=%d h=%d)", ctx->idx, crop_x, crop_y, crop_w, crop_h); @@ -169,8 +171,10 @@ static void* st20p_tx_thread(void* arg) { * Path B yuv_frame is already crop-sized (strip) → no offset needed. */ int crop_x = (ctx->use_ffmpeg == true) ? ctx->crop_x_offset : 0; int crop_y = (ctx->use_ffmpeg == true) ? ctx->crop_y_offset : 0; - int crop_w = ctx->crop_width > 0 ? ctx->crop_width : (int)ctx->app->width; - int crop_h = ctx->crop_height > 0 ? ctx->crop_height : (int)ctx->app->height; + int eff_w = (int)(ctx->app->scale_width > 0 ? ctx->app->scale_width : ctx->app->width); + int eff_h = (int)(ctx->app->scale_height > 0 ? ctx->app->scale_height : ctx->app->height); + int crop_w = ctx->crop_width > 0 ? ctx->crop_width : eff_w; + int crop_h = ctx->crop_height > 0 ? ctx->crop_height : eff_h; while (ctx->app->exit == false && session_manager_should_exit() == false) { const AVFrame* frame = tx_fetch_next_frame(ctx); @@ -217,13 +221,15 @@ int create_st20p_tx_session(session_manager_t* manager, struct dvledtx_context* /* Fallback: divide the frame width evenly across all sessions */ if (ctx->crop_width == 0 || ctx->crop_height == 0) { int total = app->st20p_sessions > 0 ? app->st20p_sessions : 1; - int strip_w = (int)app->width / total; + int eff_w = (int)(app->scale_width > 0 ? app->scale_width : app->width); + int eff_h = (int)(app->scale_height > 0 ? app->scale_height : app->height); + int strip_w = eff_w / total; ctx->crop_x_offset = session_idx * strip_w; ctx->crop_y_offset = 0; ctx->crop_width = (session_idx == total - 1) - ? ((int)app->width - ctx->crop_x_offset) + ? (eff_w - ctx->crop_x_offset) : strip_w; - ctx->crop_height = (int)app->height; + ctx->crop_height = eff_h; } LOG_INFO("ST20P TX session %d: crop rect x=%d y=%d w=%d h=%d", session_idx, ctx->crop_x_offset, ctx->crop_y_offset, diff --git a/src/ffmpeg/ffmpeg_decoder.c b/src/ffmpeg/ffmpeg_decoder.c index 7cee9dc..5274a5c 100644 --- a/src/ffmpeg/ffmpeg_decoder.c +++ b/src/ffmpeg/ffmpeg_decoder.c @@ -321,9 +321,11 @@ static void close_ffmpeg_decoder( * ========================================================================= */ int open_shared_ffmpeg(struct shared_decode_ctx* dec, const char* filename) { const struct dvledtx_context* app = dec->app; + int target_w = (int)(app->scale_width > 0 ? app->scale_width : app->width); + int target_h = (int)(app->scale_height > 0 ? app->scale_height : app->height); return open_ffmpeg_decoder( filename, "Shared decode", - app->fmt, (int)app->width, (int)app->height, + app->fmt, target_w, target_h, &dec->fmt_ctx, &dec->codec_ctx, &dec->sws_ctx, &dec->av_frame, &dec->yuv_frame, &dec->av_packet, &dec->video_stream_idx); @@ -341,9 +343,11 @@ void close_shared_ffmpeg(struct shared_decode_ctx* dec) { static int open_ffmpeg_source(struct st20p_tx_ctx* ctx, const char* filename) { char log_prefix[64]; snprintf(log_prefix, sizeof(log_prefix), "ST20P TX(%d)", ctx->idx); + int target_w = (int)(ctx->app->scale_width > 0 ? ctx->app->scale_width : ctx->app->width); + int target_h = (int)(ctx->app->scale_height > 0 ? ctx->app->scale_height : ctx->app->height); int ret = open_ffmpeg_decoder( filename, log_prefix, - ctx->app->fmt, (int)ctx->app->width, (int)ctx->app->height, + ctx->app->fmt, target_w, target_h, &ctx->fmt_ctx, &ctx->codec_ctx, &ctx->sws_ctx, &ctx->av_frame, &ctx->yuv_frame, &ctx->av_packet, &ctx->video_stream_idx); diff --git a/src/util/config_reader.c b/src/util/config_reader.c index c475e07..0e2c848 100644 --- a/src/util/config_reader.c +++ b/src/util/config_reader.c @@ -261,6 +261,8 @@ int parse_tx_config(const char* config_file, struct dvledtx_config* config) { int v; v = extract_json_int(video_obj, video_end, "width"); if (v > 0) config->width = v; v = extract_json_int(video_obj, video_end, "height"); if (v > 0) config->height = v; + v = extract_json_int(video_obj, video_end, "scale_width"); if (v > 0) config->scale_width = v; + v = extract_json_int(video_obj, video_end, "scale_height"); if (v > 0) config->scale_height = v; v = extract_json_int(video_obj, video_end, "fps"); if (v > 0) config->fps = v; extract_json_string(video_obj, video_end, "fmt", config->fmt, sizeof(config->fmt)); extract_json_string(video_obj, video_end, "tx_url", config->tx_url, sizeof(config->tx_url)); @@ -427,6 +429,28 @@ int validate_tx_config(const struct dvledtx_config* config) { return -1; } + /* Scale dimensions validation (optional — 0 means no scaling) */ + if (config->scale_width != 0 || config->scale_height != 0) { + if (config->scale_width == 0 || config->scale_height == 0) { + LOG_ERROR("scale_width and scale_height must both be set or both omitted"); + return -1; + } + if (config->scale_width > 3840 || config->scale_height > 2160) { + LOG_ERROR("scale resolution %ux%u exceeds maximum 3840x2160", + (unsigned)config->scale_width, (unsigned)config->scale_height); + return -1; + } + if (config->scale_width % 2 != 0) { + LOG_ERROR("scale_width %u must be even for YUV formats", + (unsigned)config->scale_width); + return -1; + } + } + + /* Effective output dimensions (after scaling) for crop validation */ + uint32_t eff_width = config->scale_width > 0 ? config->scale_width : config->width; + uint32_t eff_height = config->scale_height > 0 ? config->scale_height : config->height; + /* FPS validation */ if (config->fps != 25 && config->fps != 30 && config->fps != 50 && config->fps != 60) { @@ -494,16 +518,16 @@ int validate_tx_config(const struct dvledtx_config* config) { s->crop_w, s->crop_h); return -1; } - if ((uint32_t)s->crop_x + (uint32_t)s->crop_w > config->width) { - LOG_ERROR("session %d: crop x=%d + w=%d = %u exceeds video width %u", + if ((uint32_t)s->crop_x + (uint32_t)s->crop_w > eff_width) { + LOG_ERROR("session %d: crop x=%d + w=%d = %u exceeds effective width %u", i, s->crop_x, s->crop_w, - (uint32_t)s->crop_x + (uint32_t)s->crop_w, config->width); + (uint32_t)s->crop_x + (uint32_t)s->crop_w, eff_width); return -1; } - if ((uint32_t)s->crop_y + (uint32_t)s->crop_h > config->height) { - LOG_ERROR("session %d: crop y=%d + h=%d = %u exceeds video height %u", + if ((uint32_t)s->crop_y + (uint32_t)s->crop_h > eff_height) { + LOG_ERROR("session %d: crop y=%d + h=%d = %u exceeds effective height %u", i, s->crop_y, s->crop_h, - (uint32_t)s->crop_y + (uint32_t)s->crop_h, config->height); + (uint32_t)s->crop_y + (uint32_t)s->crop_h, eff_height); return -1; } if (s->crop_w % 2 != 0) { @@ -589,6 +613,8 @@ int load_and_apply_config(struct dvledtx_context* app, const char* config_file) /* Video */ app->width = config.width; app->height = config.height; + app->scale_width = config.scale_width; + app->scale_height = config.scale_height; app->fps = config.fps; if (strcmp(config.fmt, "yuv422p10le") == 0) app->fmt = AV_PIX_FMT_YUV422P10LE; @@ -636,9 +662,16 @@ int load_and_apply_config(struct dvledtx_context* app, const char* config_file) config_file, config.interface_name, config.interface_sip[0] ? config.interface_sip : "dhcp", config.interface_dip); - LOG_INFO("Video: %dx%d %dfps %s tx_url=%s", - config.width, config.height, config.fps, config.fmt, - config.tx_url[0] ? config.tx_url : ""); + if (config.scale_width > 0 && config.scale_height > 0) + LOG_INFO("Video: %ux%u -> scale %ux%u %dfps %s tx_url=%s", + config.width, config.height, + config.scale_width, config.scale_height, + config.fps, config.fmt, + config.tx_url[0] ? config.tx_url : ""); + else + LOG_INFO("Video: %ux%u %dfps %s tx_url=%s", + config.width, config.height, config.fps, config.fmt, + config.tx_url[0] ? config.tx_url : ""); for (int i = 0; i < config.session_count; i++) LOG_INFO(" Session %d: udp_port=%u pt=%u crop=[%d,%d %dx%d]", i, config.sessions[i].udp_port, config.sessions[i].payload_type, diff --git a/tests/test_config_reader.c b/tests/test_config_reader.c index 241536b..dab0f45 100644 --- a/tests/test_config_reader.c +++ b/tests/test_config_reader.c @@ -640,6 +640,117 @@ static void test_validate_4k_height_exceeds_max_fails(void **state) assert_int_equal(validate_tx_config(&cfg), -1); } +/* ========================================================================== + * validate_tx_config — scaling tests + * ========================================================================== */ + +static void test_validate_scale_upscale_1080p_to_4k_passes(void **state) +{ + (void)state; + struct dvledtx_config cfg; + fill_valid_config(&cfg); + cfg.width = 1920; + cfg.height = 1080; + cfg.scale_width = 3840; + cfg.scale_height = 2160; + cfg.sessions[0].crop_w = 3840; + cfg.sessions[0].crop_h = 2160; + assert_int_equal(validate_tx_config(&cfg), 0); +} + +static void test_validate_scale_downscale_4k_to_1080p_passes(void **state) +{ + (void)state; + struct dvledtx_config cfg; + fill_valid_config(&cfg); + cfg.width = 3840; + cfg.height = 2160; + cfg.scale_width = 1920; + cfg.scale_height = 1080; + cfg.sessions[0].crop_w = 1920; + cfg.sessions[0].crop_h = 1080; + assert_int_equal(validate_tx_config(&cfg), 0); +} + +static void test_validate_scale_width_only_fails(void **state) +{ + (void)state; + struct dvledtx_config cfg; + fill_valid_config(&cfg); + cfg.scale_width = 3840; + cfg.scale_height = 0; + assert_int_equal(validate_tx_config(&cfg), -1); +} + +static void test_validate_scale_height_only_fails(void **state) +{ + (void)state; + struct dvledtx_config cfg; + fill_valid_config(&cfg); + cfg.scale_width = 0; + cfg.scale_height = 2160; + assert_int_equal(validate_tx_config(&cfg), -1); +} + +static void test_validate_scale_exceeds_max_fails(void **state) +{ + (void)state; + struct dvledtx_config cfg; + fill_valid_config(&cfg); + cfg.scale_width = 4096; + cfg.scale_height = 2160; + assert_int_equal(validate_tx_config(&cfg), -1); +} + +static void test_validate_scale_odd_width_fails(void **state) +{ + (void)state; + struct dvledtx_config cfg; + fill_valid_config(&cfg); + cfg.scale_width = 1921; + cfg.scale_height = 1080; + assert_int_equal(validate_tx_config(&cfg), -1); +} + +static void test_validate_scale_crop_exceeds_scaled_dims_fails(void **state) +{ + (void)state; + struct dvledtx_config cfg; + fill_valid_config(&cfg); + cfg.width = 3840; + cfg.height = 2160; + cfg.scale_width = 1920; + cfg.scale_height = 1080; + /* crop is 3840x2160 but effective dims are 1920x1080 */ + cfg.sessions[0].crop_w = 3840; + cfg.sessions[0].crop_h = 2160; + assert_int_equal(validate_tx_config(&cfg), -1); +} + +static void test_validate_scale_crop_within_scaled_dims_passes(void **state) +{ + (void)state; + struct dvledtx_config cfg; + fill_valid_config(&cfg); + cfg.width = 3840; + cfg.height = 2160; + cfg.scale_width = 2560; + cfg.scale_height = 1440; + cfg.sessions[0].crop_w = 2560; + cfg.sessions[0].crop_h = 1440; + assert_int_equal(validate_tx_config(&cfg), 0); +} + +static void test_validate_no_scale_passes(void **state) +{ + (void)state; + struct dvledtx_config cfg; + fill_valid_config(&cfg); + cfg.scale_width = 0; + cfg.scale_height = 0; + assert_int_equal(validate_tx_config(&cfg), 0); +} + static void test_validate_duplicate_udp_ports_fails(void **state) { (void)state; @@ -1178,6 +1289,15 @@ int main(void) cmocka_unit_test(test_validate_2k_resolution_passes), cmocka_unit_test(test_validate_4k_resolution_passes), cmocka_unit_test(test_validate_4k_height_exceeds_max_fails), + cmocka_unit_test(test_validate_scale_upscale_1080p_to_4k_passes), + cmocka_unit_test(test_validate_scale_downscale_4k_to_1080p_passes), + cmocka_unit_test(test_validate_scale_width_only_fails), + cmocka_unit_test(test_validate_scale_height_only_fails), + cmocka_unit_test(test_validate_scale_exceeds_max_fails), + cmocka_unit_test(test_validate_scale_odd_width_fails), + cmocka_unit_test(test_validate_scale_crop_exceeds_scaled_dims_fails), + cmocka_unit_test(test_validate_scale_crop_within_scaled_dims_passes), + cmocka_unit_test(test_validate_no_scale_passes), cmocka_unit_test(test_validate_duplicate_udp_ports_fails), cmocka_unit_test(test_validate_crop_x_misaligned_for_yuv422_fails), cmocka_unit_test(test_validate_tx_url_nonexistent_file_fails), From 7ba414c9e14c7ef146b6c3dded4e66bbeadabbbc Mon Sep 17 00:00:00 2001 From: sunilnom Date: Wed, 17 Jun 2026 13:29:51 +0530 Subject: [PATCH 3/6] fix: address PR review comments - Move scale_width, scale_height, fps, fmt to new tx_video section in all config JSON files - Fix scale dimension validation to check chroma alignment per pixel format (e.g. yuv420 requires even height, gbrp* has no alignment) - Fix absolute tx_url paths in tx_4k configs to use relative filenames - Default missing payload_type to 96 instead of failing parse - Update README scaling note to document format alignment constraints - Add test for odd scale_height rejection with yuv420 format - Update tests to use new tx_video JSON section --- README.md | 2 +- config/tx_2k_multi_session.json | 6 ++-- config/tx_2k_single_session.json | 6 ++-- config/tx_4k_multi_session.json | 6 ++-- config/tx_4k_single_session.json | 6 ++-- config/tx_fullhd_multi_session.json | 6 ++-- config/tx_fullhd_single_session.json | 12 ++++--- src/util/config_reader.c | 53 ++++++++++++++++++++-------- tests/test_config_reader.c | 27 +++++++++++--- 9 files changed, 88 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 97e3891..d176f08 100644 --- a/README.md +++ b/README.md @@ -271,7 +271,7 @@ Log output when scaling is active: [INFO ] Session 0: udp_port=20000 pt=96 crop=[0,0 3840x2160] ``` -> **Note:** When `scale_width`/`scale_height` are set, crop bounds are validated against the scaled dimensions, not the source dimensions. Both fields must be specified together. +> **Note:** When `scale_width`/`scale_height` are set, crop bounds are validated against the scaled dimensions, not the source dimensions. Both fields must be specified together, and the scaled dimensions must satisfy the pixel format's chroma-alignment constraints (e.g. `yuv420` requires even width and height; `yuv422*` requires even width). ## Performance Considerations diff --git a/config/tx_2k_multi_session.json b/config/tx_2k_multi_session.json index c1213ee..8a8820a 100644 --- a/config/tx_2k_multi_session.json +++ b/config/tx_2k_multi_session.json @@ -10,11 +10,13 @@ "video": { "width": 2560, "height": 1440, + "tx_url": "ball_2k_yuv420p_30fps_5min.mp4" + }, + "tx_video": { "scale_width": 2560, "scale_height": 1440, "fps": 30, - "fmt": "yuv422p10le", - "tx_url": "ball_2k_yuv420p_30fps_5min.mp4" + "fmt": "yuv422p10le" }, "tx_sessions": [ { diff --git a/config/tx_2k_single_session.json b/config/tx_2k_single_session.json index d1d6caa..3104fd3 100644 --- a/config/tx_2k_single_session.json +++ b/config/tx_2k_single_session.json @@ -10,11 +10,13 @@ "video": { "width": 2560, "height": 1440, + "tx_url": "ball_2k_yuv420p_30fps_5min.mp4" + }, + "tx_video": { "scale_width": 2560, "scale_height": 1440, "fps": 30, - "fmt": "yuv422p10le", - "tx_url": "ball_2k_yuv420p_30fps_5min.mp4" + "fmt": "yuv422p10le" }, "tx_sessions": [ { diff --git a/config/tx_4k_multi_session.json b/config/tx_4k_multi_session.json index 47293cd..0b14087 100644 --- a/config/tx_4k_multi_session.json +++ b/config/tx_4k_multi_session.json @@ -10,11 +10,13 @@ "video": { "width": 3840, "height": 2160, + "tx_url": "ball_4k_gbrp10le_60fps_5min.mp4" + }, + "tx_video": { "scale_width": 1920, "scale_height": 1080, "fps": 30, - "fmt": "yuv422p10le", - "tx_url": "/home/intel/workspace/sample/ball_4k_gbrp10le_60fps_5min.mp4" + "fmt": "yuv422p10le" }, "tx_sessions": [ { diff --git a/config/tx_4k_single_session.json b/config/tx_4k_single_session.json index 5e112f9..004de34 100644 --- a/config/tx_4k_single_session.json +++ b/config/tx_4k_single_session.json @@ -10,11 +10,13 @@ "video": { "width": 3840, "height": 2160, + "tx_url": "ball_4k_gbrp10le_60fps_5min.mp4" + }, + "tx_video": { "scale_width": 900, "scale_height": 600, "fps": 60, - "fmt": "gbrp10le", - "tx_url": "/home/intel/workspace/sample/ball_4k_gbrp10le_60fps_5min.mp4" + "fmt": "gbrp10le" }, "tx_sessions": [ { diff --git a/config/tx_fullhd_multi_session.json b/config/tx_fullhd_multi_session.json index bc5a435..f76470c 100644 --- a/config/tx_fullhd_multi_session.json +++ b/config/tx_fullhd_multi_session.json @@ -10,11 +10,13 @@ "video": { "width": 1920, "height": 1080, + "tx_url": "bbb_sunflower_1080p_30fps_normal.mp4" + }, + "tx_video": { "scale_width": 1920, "scale_height": 1080, "fps": 30, - "fmt": "yuv422p10le", - "tx_url": "bbb_sunflower_1080p_30fps_normal.mp4" + "fmt": "yuv422p10le" }, "tx_sessions": [ { diff --git a/config/tx_fullhd_single_session.json b/config/tx_fullhd_single_session.json index e001701..32b9925 100644 --- a/config/tx_fullhd_single_session.json +++ b/config/tx_fullhd_single_session.json @@ -2,7 +2,7 @@ "log_file": "dvledtx.log", "interfaces": [ { - "name": "0000:03:00.1", + "name": "0000:06:00.0", "sip": "192.168.50.29", "dip": "239.168.85.20" } @@ -10,17 +10,19 @@ "video": { "width": 3840, "height": 2160, + "tx_url": "bbb_sunflower_1080p_30fps_normal.mp4" + }, + "tx_video": { "scale_width": 3840, "scale_height": 2160, - "fps": 60, - "fmt": "gbrp10le", - "tx_url": "ball_4k_gbrp10le_30fps_5min.mp4" + "fps": 30, + "fmt": "yuv422p10le" }, "tx_sessions": [ { "udp_port": 20000, "payload_type": 96, - "crop": { "x": 0, "y": 0, "w": 3840, "h": 2160 } + "crop": { "x": 0, "y": 0, "w": 1920, "h": 1080 } } ] } diff --git a/src/util/config_reader.c b/src/util/config_reader.c index 0e2c848..e9d7b48 100644 --- a/src/util/config_reader.c +++ b/src/util/config_reader.c @@ -199,7 +199,8 @@ int peek_config_log_file(const char* config_file, char* out_buf, size_t out_size * Expected JSON structure: * { * "interfaces": [ { "name": "...", "sip": "...", "dip": "..." } ], - * "video": { "width": N, "height": N, "fps": N, "fmt": "...", "tx_url": "..." }, + * "video": { "width": N, "height": N, "tx_url": "..." }, + * "tx_video": { "scale_width": N, "scale_height": N, "fps": N, "fmt": "..." }, * "log_file": "/path/to/dvledtx.log", (optional — omit for console-only logging) * "tx_sessions": [ * { "udp_port": N, "payload_type": N, "crop": { "x":N, "y":N, "w":N, "h":N } }, @@ -261,13 +262,21 @@ int parse_tx_config(const char* config_file, struct dvledtx_config* config) { int v; v = extract_json_int(video_obj, video_end, "width"); if (v > 0) config->width = v; v = extract_json_int(video_obj, video_end, "height"); if (v > 0) config->height = v; - v = extract_json_int(video_obj, video_end, "scale_width"); if (v > 0) config->scale_width = v; - v = extract_json_int(video_obj, video_end, "scale_height"); if (v > 0) config->scale_height = v; - v = extract_json_int(video_obj, video_end, "fps"); if (v > 0) config->fps = v; - extract_json_string(video_obj, video_end, "fmt", config->fmt, sizeof(config->fmt)); extract_json_string(video_obj, video_end, "tx_url", config->tx_url, sizeof(config->tx_url)); } + /* --- tx_video block (transmission parameters) --- */ + const char* tx_video_obj = find_object(json, buf_end, "tx_video"); + if (tx_video_obj != NULL) { + const char* tx_video_end = find_object_end(tx_video_obj, buf_end); + if (tx_video_end == NULL) tx_video_end = buf_end; + int v; + v = extract_json_int(tx_video_obj, tx_video_end, "scale_width"); if (v > 0) config->scale_width = v; + v = extract_json_int(tx_video_obj, tx_video_end, "scale_height"); if (v > 0) config->scale_height = v; + v = extract_json_int(tx_video_obj, tx_video_end, "fps"); if (v > 0) config->fps = v; + extract_json_string(tx_video_obj, tx_video_end, "fmt", config->fmt, sizeof(config->fmt)); + } + /* --- optional top-level log_file --- */ extract_json_string(json, buf_end, "log_file", config->log_file, sizeof(config->log_file)); @@ -311,10 +320,7 @@ int parse_tx_config(const char* config_file, struct dvledtx_config* config) { if (v > 0 && v <= 255) { s->payload_type = (uint8_t)v; } else { - LOG_ERROR("session %d: payload_type not set or invalid (%d)", - config->session_count, v); - free(json); - return -1; + s->payload_type = 96; /* default to 96 (first dynamic RTP payload type) */ } /* crop sub-object */ @@ -440,10 +446,26 @@ int validate_tx_config(const struct dvledtx_config* config) { (unsigned)config->scale_width, (unsigned)config->scale_height); return -1; } - if (config->scale_width % 2 != 0) { - LOG_ERROR("scale_width %u must be even for YUV formats", - (unsigned)config->scale_width); - return -1; + /* Validate scaled dimensions against pixel format chroma alignment */ + { + const char* fmt_lookup = config->fmt[0] ? config->fmt : "yuv422p10le"; + if (strcmp(fmt_lookup, "yuv420") == 0) fmt_lookup = "yuv420p"; + enum AVPixelFormat pix_fmt = av_get_pix_fmt(fmt_lookup); + const AVPixFmtDescriptor* desc = + (pix_fmt != AV_PIX_FMT_NONE) ? av_pix_fmt_desc_get(pix_fmt) : NULL; + int x_align = desc ? (1 << desc->log2_chroma_w) : 2; + int y_align = desc ? (1 << desc->log2_chroma_h) : 1; + + if (x_align > 1 && config->scale_width % x_align != 0) { + LOG_ERROR("scale_width %u must be a multiple of %d for pixel format '%s'", + (unsigned)config->scale_width, x_align, config->fmt); + return -1; + } + if (y_align > 1 && config->scale_height % y_align != 0) { + LOG_ERROR("scale_height %u must be a multiple of %d for pixel format '%s'", + (unsigned)config->scale_height, y_align, config->fmt); + return -1; + } } } @@ -540,8 +562,9 @@ int validate_tx_config(const struct dvledtx_config* config) { * Use the pixel format descriptor (if available) to determine * the required alignment from log2_chroma_w/h. */ { - enum AVPixelFormat pix_fmt = av_get_pix_fmt( - config->fmt[0] ? config->fmt : "yuv422p10le"); + const char* fmt_lookup = config->fmt[0] ? config->fmt : "yuv422p10le"; + if (strcmp(fmt_lookup, "yuv420") == 0) fmt_lookup = "yuv420p"; + enum AVPixelFormat pix_fmt = av_get_pix_fmt(fmt_lookup); const AVPixFmtDescriptor* desc = (pix_fmt != AV_PIX_FMT_NONE) ? av_pix_fmt_desc_get(pix_fmt) : NULL; int x_align = desc ? (1 << desc->log2_chroma_w) : 2; diff --git a/tests/test_config_reader.c b/tests/test_config_reader.c index dab0f45..ca9648a 100644 --- a/tests/test_config_reader.c +++ b/tests/test_config_reader.c @@ -560,7 +560,7 @@ static void test_parse_session_udp_port_exceeds_65535_fails(void **state) assert_int_equal(ret, -1); } -static void test_parse_session_missing_payload_type_fails(void **state) +static void test_parse_session_missing_payload_type_defaults_to_96(void **state) { (void)state; char *path = write_tmpfile( @@ -574,7 +574,8 @@ static void test_parse_session_missing_payload_type_fails(void **state) struct dvledtx_config cfg; int ret = parse_tx_config(path, &cfg); unlink(path); free(path); - assert_int_equal(ret, -1); + assert_int_equal(ret, 0); + assert_int_equal(cfg.sessions[0].payload_type, 96); } static void test_parse_session_no_crop_object_fails(void **state) @@ -712,6 +713,19 @@ static void test_validate_scale_odd_width_fails(void **state) assert_int_equal(validate_tx_config(&cfg), -1); } +static void test_validate_scale_odd_height_yuv420_fails(void **state) +{ + (void)state; + struct dvledtx_config cfg; + fill_valid_config(&cfg); + strncpy(cfg.fmt, "yuv420", sizeof(cfg.fmt) - 1); + cfg.scale_width = 1920; + cfg.scale_height = 1081; /* yuv420 requires even height */ + cfg.sessions[0].crop_w = 1920; + cfg.sessions[0].crop_h = 1080; + assert_int_equal(validate_tx_config(&cfg), -1); +} + static void test_validate_scale_crop_exceeds_scaled_dims_fails(void **state) { (void)state; @@ -1138,7 +1152,8 @@ static void test_load_and_apply_config_nonexistent_file_returns_minus1(void **st "{" \ " \"interfaces\": [{\"name\":\"0000:06:00.0\"," \ " \"sip\":\"192.168.50.29\",\"dip\":\"239.168.85.20\"}]," \ - " \"video\": {\"width\":1920,\"height\":1080,\"fps\":30,\"fmt\":\"" fmt_str "\"}," \ + " \"video\": {\"width\":1920,\"height\":1080}," \ + " \"tx_video\": {\"fps\":30,\"fmt\":\"" fmt_str "\"}," \ " \"tx_sessions\": [{\"udp_port\":20000,\"payload_type\":96," \ " \"crop\":{\"x\":0,\"y\":0,\"w\":1920,\"h\":1080}}]" \ "}" @@ -1221,7 +1236,8 @@ static void test_load_and_apply_config_copies_log_file(void **state) "{" " \"log_file\": \"myapp.log\"," " \"interfaces\": [{\"name\":\"0000:06:00.0\",\"sip\":\"192.168.50.29\",\"dip\":\"239.168.85.20\"}]," - " \"video\": {\"width\":1920,\"height\":1080,\"fps\":25,\"fmt\":\"yuv422p10le\"}," + " \"video\": {\"width\":1920,\"height\":1080}," + " \"tx_video\": {\"fps\":25,\"fmt\":\"yuv422p10le\"}," " \"tx_sessions\": [{\"udp_port\":20000,\"payload_type\":96," " \"crop\":{\"x\":0,\"y\":0,\"w\":1920,\"h\":1080}}]" "}"); @@ -1257,7 +1273,7 @@ int main(void) cmocka_unit_test(test_parse_returns_zero_fields_when_video_missing), cmocka_unit_test(test_parse_session_missing_udp_port_fails), cmocka_unit_test(test_parse_session_udp_port_exceeds_65535_fails), - cmocka_unit_test(test_parse_session_missing_payload_type_fails), + cmocka_unit_test(test_parse_session_missing_payload_type_defaults_to_96), cmocka_unit_test(test_parse_session_no_crop_object_fails), cmocka_unit_test(test_parse_session_negative_crop_x_fails), cmocka_unit_test(test_parse_session_zero_crop_w_fails), @@ -1295,6 +1311,7 @@ int main(void) cmocka_unit_test(test_validate_scale_height_only_fails), cmocka_unit_test(test_validate_scale_exceeds_max_fails), cmocka_unit_test(test_validate_scale_odd_width_fails), + cmocka_unit_test(test_validate_scale_odd_height_yuv420_fails), cmocka_unit_test(test_validate_scale_crop_exceeds_scaled_dims_fails), cmocka_unit_test(test_validate_scale_crop_within_scaled_dims_passes), cmocka_unit_test(test_validate_no_scale_passes), From 981f4d9d04ed8330c357f4427e4d6bd9a0bf53d5 Mon Sep 17 00:00:00 2001 From: sunilnom Date: Wed, 17 Jun 2026 14:29:28 +0530 Subject: [PATCH 4/6] docs: update README to reflect tx_video section and optional payload_type - Move scale_width/scale_height/fps/fmt to tx_video section in config table - Update JSON examples to use tx_video section - Mark payload_type as optional (defaults to 96) - Remove payload_type from config JSON files --- README.md | 22 +++++++++++++--------- config/tx_2k_multi_session.json | 7 ++----- config/tx_2k_single_session.json | 1 - config/tx_4k_multi_session.json | 3 --- config/tx_4k_single_session.json | 5 ++--- config/tx_fullhd_multi_session.json | 3 --- config/tx_fullhd_single_session.json | 1 - 7 files changed, 17 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index d176f08..edff75f 100644 --- a/README.md +++ b/README.md @@ -131,13 +131,13 @@ dvledtx uses a JSON config file with three sections: | | `dip` | Destination multicast IP address | | **video** | `width` | Source frame width in pixels | | | `height` | Source frame height in pixels | -| | `scale_width` | (Optional) Output width after scaling | +| | `tx_url` | Path to the source video file | +| **tx_video** | `scale_width` | (Optional) Output width after scaling | | | `scale_height` | (Optional) Output height after scaling | | | `fps` | Frames per second (25, 30, 50, 60) | | | `fmt` | Pixel format (see [Supported Formats](#supported-formats)) | -| | `tx_url` | Path to the source video file | | **tx_sessions[]** | `udp_port` | UDP port for the session | -| | `payload_type` | RTP payload type (typically 96) | +| | `payload_type` | (Optional) RTP payload type — defaults to `96` | | | `crop` | Region to transmit: `x`, `y`, `w`, `h` in pixels | Example (`config/tx_fullhd_single_session.json`): @@ -148,12 +148,14 @@ Example (`config/tx_fullhd_single_session.json`): { "name": "0000:06:00.0", "sip": "192.168.50.29", "dip": "239.168.85.20" } ], "video": { - "width": 1920, "height": 1080, "fps": 30, - "fmt": "yuv422p10le", + "width": 1920, "height": 1080, "tx_url": "bbb_sunflower_1080p_30fps_normal.mp4" }, + "tx_video": { + "fps": 30, "fmt": "yuv422p10le" + }, "tx_sessions": [ - { "udp_port": 20000, "payload_type": 96, "crop": { "x": 0, "y": 0, "w": 1920, "h": 1080 } } + { "udp_port": 20000, "crop": { "x": 0, "y": 0, "w": 1920, "h": 1080 } } ] } ``` @@ -255,12 +257,14 @@ Example — upscale 1080p source to 4K output: { "video": { "width": 1920, "height": 1080, - "scale_width": 3840, "scale_height": 2160, - "fps": 30, "fmt": "yuv422p10le", "tx_url": "source_1080p.mp4" }, + "tx_video": { + "scale_width": 3840, "scale_height": 2160, + "fps": 30, "fmt": "yuv422p10le" + }, "tx_sessions": [ - { "udp_port": 20000, "payload_type": 96, "crop": { "x": 0, "y": 0, "w": 3840, "h": 2160 } } + { "udp_port": 20000, "crop": { "x": 0, "y": 0, "w": 3840, "h": 2160 } } ] } ``` diff --git a/config/tx_2k_multi_session.json b/config/tx_2k_multi_session.json index 8a8820a..ebaea16 100644 --- a/config/tx_2k_multi_session.json +++ b/config/tx_2k_multi_session.json @@ -2,7 +2,7 @@ "log_file": "dvledtx.log", "interfaces": [ { - "name": "0000:03:00.1", + "name": "0000:06:00.0", "sip": "192.168.50.29", "dip": "239.168.85.20" } @@ -10,7 +10,7 @@ "video": { "width": 2560, "height": 1440, - "tx_url": "ball_2k_yuv420p_30fps_5min.mp4" + "tx_url": "bbb_sunflower_1080p_30fps_normal.mp4" }, "tx_video": { "scale_width": 2560, @@ -21,17 +21,14 @@ "tx_sessions": [ { "udp_port": 20000, - "payload_type": 96, "crop": { "x": 0, "y": 0, "w": 854, "h": 1440 } }, { "udp_port": 20002, - "payload_type": 96, "crop": { "x": 854, "y": 0, "w": 854, "h": 1440 } }, { "udp_port": 20004, - "payload_type": 96, "crop": { "x": 1708, "y": 0, "w": 852, "h": 1440 } } ] diff --git a/config/tx_2k_single_session.json b/config/tx_2k_single_session.json index 3104fd3..0be6239 100644 --- a/config/tx_2k_single_session.json +++ b/config/tx_2k_single_session.json @@ -21,7 +21,6 @@ "tx_sessions": [ { "udp_port": 20000, - "payload_type": 96, "crop": { "x": 0, "y": 0, "w": 2560, "h": 1440 } } ] diff --git a/config/tx_4k_multi_session.json b/config/tx_4k_multi_session.json index 0b14087..63800f2 100644 --- a/config/tx_4k_multi_session.json +++ b/config/tx_4k_multi_session.json @@ -21,17 +21,14 @@ "tx_sessions": [ { "udp_port": 20000, - "payload_type": 96, "crop": { "x": 0, "y": 0, "w": 640, "h": 1080 } }, { "udp_port": 20002, - "payload_type": 96, "crop": { "x": 640, "y": 0, "w": 640, "h": 1080 } }, { "udp_port": 20004, - "payload_type": 96, "crop": { "x": 1280, "y": 0, "w": 640, "h": 1080 } } ] diff --git a/config/tx_4k_single_session.json b/config/tx_4k_single_session.json index 004de34..5fa2bbc 100644 --- a/config/tx_4k_single_session.json +++ b/config/tx_4k_single_session.json @@ -15,13 +15,12 @@ "tx_video": { "scale_width": 900, "scale_height": 600, - "fps": 60, - "fmt": "gbrp10le" + "fps": 30, + "fmt": "yuv422p10le" }, "tx_sessions": [ { "udp_port": 20000, - "payload_type": 96, "crop": { "x": 0, "y": 0, "w": 900, "h": 600 } } ] diff --git a/config/tx_fullhd_multi_session.json b/config/tx_fullhd_multi_session.json index f76470c..3e98b9e 100644 --- a/config/tx_fullhd_multi_session.json +++ b/config/tx_fullhd_multi_session.json @@ -21,17 +21,14 @@ "tx_sessions": [ { "udp_port": 20000, - "payload_type": 96, "crop": { "x": 0, "y": 0, "w": 640, "h": 1080 } }, { "udp_port": 20002, - "payload_type": 96, "crop": { "x": 640, "y": 0, "w": 640, "h": 1080 } }, { "udp_port": 20004, - "payload_type": 96, "crop": { "x": 1280, "y": 0, "w": 640, "h": 1080 } } ] diff --git a/config/tx_fullhd_single_session.json b/config/tx_fullhd_single_session.json index 32b9925..2939030 100644 --- a/config/tx_fullhd_single_session.json +++ b/config/tx_fullhd_single_session.json @@ -21,7 +21,6 @@ "tx_sessions": [ { "udp_port": 20000, - "payload_type": 96, "crop": { "x": 0, "y": 0, "w": 1920, "h": 1080 } } ] From bd0534efed8bffa1010102dbce39ac2c004d3d60 Mon Sep 17 00:00:00 2001 From: sunilnom Date: Wed, 17 Jun 2026 14:42:56 +0530 Subject: [PATCH 5/6] docs: clarify payload_type defaults to 96 if not present --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index edff75f..9178494 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ dvledtx uses a JSON config file with three sections: | | `fps` | Frames per second (25, 30, 50, 60) | | | `fmt` | Pixel format (see [Supported Formats](#supported-formats)) | | **tx_sessions[]** | `udp_port` | UDP port for the session | -| | `payload_type` | (Optional) RTP payload type — defaults to `96` | +| | `payload_type` | (Optional) RTP payload type — defaults to `96` if not present | | | `crop` | Region to transmit: `x`, `y`, `w`, `h` in pixels | Example (`config/tx_fullhd_single_session.json`): From fc816b81a374bb1689dbc31ddc5e76951a9dcdb1 Mon Sep 17 00:00:00 2001 From: sunilnom Date: Thu, 18 Jun 2026 13:34:50 +0530 Subject: [PATCH 6/6] chore: update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c901167..397b4a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -dvledtx.log +*.log