From e86a05a5b93b3eafab6f3e0e21d6a392473e78de Mon Sep 17 00:00:00 2001 From: roshan-ku Date: Tue, 19 May 2026 14:30:55 +0530 Subject: [PATCH 01/10] Add 12-bit pixel format support for yuv422, yuv420, yuv444, and gbr Add support for 12-bit pixel depth variants: - yuv422p12le (ST_FRAME_FMT_YUV422PLANAR12LE / ST20_FMT_YUV_422_12BIT) - yuv420p12le (ST20_FMT_YUV_420_12BIT transport format) - yuv444p12le (ST_FRAME_FMT_YUV444PLANAR12LE / ST20_FMT_YUV_444_12BIT) - gbrp12le (ST_FRAME_FMT_GBRPLANAR12LE / ST20_FMT_RGB_12BIT) Updated: - config_reader: validation whitelist and AVPixelFormat mapping - mtl_tx: get_input_format() and get_transport_format() switch cases - tests: config_reader and mtl_tx test coverage for new formats --- src/mtl/mtl_tx.c | 7 +++++++ src/util/config_reader.c | 13 +++++++++++-- tests/test_config_reader.c | 3 ++- tests/test_mtl_tx.c | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/mtl/mtl_tx.c b/src/mtl/mtl_tx.c index 3a844af..88c82fd 100644 --- a/src/mtl/mtl_tx.c +++ b/src/mtl/mtl_tx.c @@ -50,6 +50,9 @@ enum st_frame_fmt get_input_format(enum AVPixelFormat fmt) { case AV_PIX_FMT_YUV420P: return ST_FRAME_FMT_YUV420CUSTOM8; case AV_PIX_FMT_YUV444P10LE: return ST_FRAME_FMT_YUV444PLANAR10LE; case AV_PIX_FMT_GBRP10LE: return ST_FRAME_FMT_GBRPLANAR10LE; + case AV_PIX_FMT_YUV422P12LE: return ST_FRAME_FMT_YUV422PLANAR12LE; + case AV_PIX_FMT_YUV444P12LE: return ST_FRAME_FMT_YUV444PLANAR12LE; + case AV_PIX_FMT_GBRP12LE: return ST_FRAME_FMT_GBRPLANAR12LE; default: LOG_ERROR("get_input_format: unsupported AVPixelFormat %d", fmt); return (enum st_frame_fmt)-1; @@ -62,6 +65,10 @@ enum st20_fmt get_transport_format(enum AVPixelFormat fmt) { case AV_PIX_FMT_YUV420P: return ST20_FMT_YUV_420_8BIT; case AV_PIX_FMT_YUV444P10LE: return ST20_FMT_YUV_444_10BIT; case AV_PIX_FMT_GBRP10LE: return ST20_FMT_RGB_10BIT; + case AV_PIX_FMT_YUV422P12LE: return ST20_FMT_YUV_422_12BIT; + case AV_PIX_FMT_YUV420P12LE: return ST20_FMT_YUV_420_12BIT; + case AV_PIX_FMT_YUV444P12LE: return ST20_FMT_YUV_444_12BIT; + case AV_PIX_FMT_GBRP12LE: return ST20_FMT_RGB_12BIT; default: LOG_ERROR("get_transport_format: unsupported AVPixelFormat %d", fmt); return (enum st20_fmt)-1; diff --git a/src/util/config_reader.c b/src/util/config_reader.c index dd7b9cb..488a476 100644 --- a/src/util/config_reader.c +++ b/src/util/config_reader.c @@ -439,9 +439,14 @@ int validate_tx_config(const struct dvledtx_config* config) { strcmp(config->fmt, "yuv422p10le") != 0 && strcmp(config->fmt, "yuv420") != 0 && strcmp(config->fmt, "yuv444p10le") != 0 && - strcmp(config->fmt, "gbrp10le") != 0) { + strcmp(config->fmt, "gbrp10le") != 0 && + strcmp(config->fmt, "yuv422p12le") != 0 && + strcmp(config->fmt, "yuv420p12le") != 0 && + strcmp(config->fmt, "yuv444p12le") != 0 && + strcmp(config->fmt, "gbrp12le") != 0) { LOG_ERROR("unsupported pixel format '%s'", config->fmt); - LOG_ERROR(" Supported: yuv422p10le, yuv420, yuv444p10le, gbrp10le"); + LOG_ERROR(" Supported: yuv422p10le, yuv420, yuv444p10le, gbrp10le, " + "yuv422p12le, yuv420p12le, yuv444p12le, gbrp12le"); return -1; } @@ -591,6 +596,10 @@ int load_and_apply_config(struct dvledtx_context* app, const char* config_file) else if (strcmp(config.fmt, "yuv420") == 0) app->fmt = AV_PIX_FMT_YUV420P; else if (strcmp(config.fmt, "yuv444p10le") == 0) app->fmt = AV_PIX_FMT_YUV444P10LE; else if (strcmp(config.fmt, "gbrp10le") == 0) app->fmt = AV_PIX_FMT_GBRP10LE; + else if (strcmp(config.fmt, "yuv422p12le") == 0) app->fmt = AV_PIX_FMT_YUV422P12LE; + else if (strcmp(config.fmt, "yuv420p12le") == 0) app->fmt = AV_PIX_FMT_YUV420P12LE; + else if (strcmp(config.fmt, "yuv444p12le") == 0) app->fmt = AV_PIX_FMT_YUV444P12LE; + else if (strcmp(config.fmt, "gbrp12le") == 0) app->fmt = AV_PIX_FMT_GBRP12LE; else { LOG_ERROR("Unsupported pixel format '%s'", config.fmt); return -1; diff --git a/tests/test_config_reader.c b/tests/test_config_reader.c index 5e27583..d9c415e 100644 --- a/tests/test_config_reader.c +++ b/tests/test_config_reader.c @@ -353,7 +353,8 @@ static void test_validate_unsupported_fmt_fails(void **state) static void test_validate_all_supported_fmts_pass(void **state) { (void)state; - const char *fmts[] = {"yuv422p10le", "yuv420", "yuv444p10le", "gbrp10le"}; + const char *fmts[] = {"yuv422p10le", "yuv420", "yuv444p10le", "gbrp10le", + "yuv422p12le", "yuv420p12le", "yuv444p12le", "gbrp12le"}; for (size_t i = 0; i < sizeof(fmts) / sizeof(fmts[0]); i++) { struct dvledtx_config cfg; fill_valid_config(&cfg); diff --git a/tests/test_mtl_tx.c b/tests/test_mtl_tx.c index 80a03c0..0f60d7c 100644 --- a/tests/test_mtl_tx.c +++ b/tests/test_mtl_tx.c @@ -71,6 +71,34 @@ static void test_get_transport_format_gbrp10le(void **state) ST20_FMT_RGB_10BIT); } +static void test_get_transport_format_yuv422p12le(void **state) +{ + (void)state; + assert_int_equal(get_transport_format(AV_PIX_FMT_YUV422P12LE), + ST20_FMT_YUV_422_12BIT); +} + +static void test_get_transport_format_yuv420p12le(void **state) +{ + (void)state; + assert_int_equal(get_transport_format(AV_PIX_FMT_YUV420P12LE), + ST20_FMT_YUV_420_12BIT); +} + +static void test_get_transport_format_yuv444p12le(void **state) +{ + (void)state; + assert_int_equal(get_transport_format(AV_PIX_FMT_YUV444P12LE), + ST20_FMT_YUV_444_12BIT); +} + +static void test_get_transport_format_gbrp12le(void **state) +{ + (void)state; + assert_int_equal(get_transport_format(AV_PIX_FMT_GBRP12LE), + ST20_FMT_RGB_12BIT); +} + static void test_get_transport_format_unknown_returns_error(void **state) { (void)state; @@ -370,6 +398,10 @@ int main(void) cmocka_unit_test(test_get_transport_format_yuv420p), cmocka_unit_test(test_get_transport_format_yuv444p10le), cmocka_unit_test(test_get_transport_format_gbrp10le), + cmocka_unit_test(test_get_transport_format_yuv422p12le), + cmocka_unit_test(test_get_transport_format_yuv420p12le), + cmocka_unit_test(test_get_transport_format_yuv444p12le), + cmocka_unit_test(test_get_transport_format_gbrp12le), cmocka_unit_test(test_get_transport_format_unknown_returns_error), /* get_st_fps */ From 84885881e218f94d391754ae3bbb5778f07583f4 Mon Sep 17 00:00:00 2001 From: roshan-ku Date: Tue, 19 May 2026 14:48:51 +0530 Subject: [PATCH 02/10] Add 12-bit sample config for yuv444p12le transmission test config/tx_1session_12bit.json: 1920x1080 30fps yuv444p12le config using test_12bit_yuv444p12le.mkv as source video. --- config/tx_1session_12bit.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 config/tx_1session_12bit.json diff --git a/config/tx_1session_12bit.json b/config/tx_1session_12bit.json new file mode 100644 index 0000000..55046cc --- /dev/null +++ b/config/tx_1session_12bit.json @@ -0,0 +1,24 @@ +{ + "log_file": "dvledtx_12bit.log", + "interfaces": [ + { + "name": "0000:06:00.0", + "sip": "192.168.50.29", + "dip": "239.168.85.20" + } + ], + "video": { + "width": 1920, + "height": 1080, + "fps": 30, + "fmt": "yuv444p12le", + "tx_url": "test_12bit_yuv444p12le.mkv" + }, + "tx_sessions": [ + { + "udp_port": 20000, + "payload_type": 96, + "crop": { "x": 0, "y": 0, "w": 1920, "h": 1080 } + } + ] +} From 3aa157376ad181014b62570568eee4b441cd852e Mon Sep 17 00:00:00 2001 From: roshan-ku Date: Tue, 19 May 2026 14:52:46 +0530 Subject: [PATCH 03/10] Add FFmpeg mtl_st20p plugin patch for 12-bit format support The upstream FFmpeg mtl_st20p TX muxer only supports YUV422P10LE, Y210LE, and RGB24. This patch adds support for all formats used by dvledtx: - YUV420P (8-bit 4:2:0) - YUV444P10LE, GBRP10LE (10-bit 4:4:4 / RGB) - YUV422P12LE, YUV420P12LE, YUV444P12LE, GBRP12LE (12-bit) On a new setup, apply with: ./scripts/apply-ffmpeg-12bit-patch.sh [FFMPEG_SRC_DIR] Then rebuild FFmpeg: make -j$(nproc) && sudo make install && sudo ldconfig --- patches/ffmpeg-mtl-st20p-12bit-formats.patch | 37 ++++++++++++++ scripts/apply-ffmpeg-12bit-patch.sh | 52 ++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 patches/ffmpeg-mtl-st20p-12bit-formats.patch create mode 100755 scripts/apply-ffmpeg-12bit-patch.sh diff --git a/patches/ffmpeg-mtl-st20p-12bit-formats.patch b/patches/ffmpeg-mtl-st20p-12bit-formats.patch new file mode 100644 index 0000000..725eb00 --- /dev/null +++ b/patches/ffmpeg-mtl-st20p-12bit-formats.patch @@ -0,0 +1,37 @@ +--- a/libavdevice/mtl_st20p_tx.c ++++ b/libavdevice/mtl_st20p_tx.c +@@ -106,6 +106,34 @@ + ops_tx.input_fmt = ST_FRAME_FMT_RGB8; + ops_tx.transport_fmt = ST20_FMT_RGB_8BIT; + break; ++ case AV_PIX_FMT_YUV420P: ++ ops_tx.input_fmt = ST_FRAME_FMT_YUV420CUSTOM8; ++ ops_tx.transport_fmt = ST20_FMT_YUV_420_8BIT; ++ break; ++ case AV_PIX_FMT_YUV444P10LE: ++ ops_tx.input_fmt = ST_FRAME_FMT_YUV444PLANAR10LE; ++ ops_tx.transport_fmt = ST20_FMT_YUV_444_10BIT; ++ break; ++ case AV_PIX_FMT_GBRP10LE: ++ ops_tx.input_fmt = ST_FRAME_FMT_GBRPLANAR10LE; ++ ops_tx.transport_fmt = ST20_FMT_RGB_10BIT; ++ break; ++ case AV_PIX_FMT_YUV422P12LE: ++ ops_tx.input_fmt = ST_FRAME_FMT_YUV422PLANAR12LE; ++ ops_tx.transport_fmt = ST20_FMT_YUV_422_12BIT; ++ break; ++ case AV_PIX_FMT_YUV420P12LE: ++ ops_tx.input_fmt = ST_FRAME_FMT_YUV420PLANAR8; /* no dedicated 12-bit 420 frame fmt */ ++ ops_tx.transport_fmt = ST20_FMT_YUV_420_12BIT; ++ break; ++ case AV_PIX_FMT_YUV444P12LE: ++ ops_tx.input_fmt = ST_FRAME_FMT_YUV444PLANAR12LE; ++ ops_tx.transport_fmt = ST20_FMT_YUV_444_12BIT; ++ break; ++ case AV_PIX_FMT_GBRP12LE: ++ ops_tx.input_fmt = ST_FRAME_FMT_GBRPLANAR12LE; ++ ops_tx.transport_fmt = ST20_FMT_RGB_12BIT; ++ break; + default: + err(ctx, "%s, unsupported pixel format: %d\n", __func__, s->pixel_format); + return AVERROR(EINVAL); diff --git a/scripts/apply-ffmpeg-12bit-patch.sh b/scripts/apply-ffmpeg-12bit-patch.sh new file mode 100755 index 0000000..599d0d9 --- /dev/null +++ b/scripts/apply-ffmpeg-12bit-patch.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# SPDX-License-Identifier: BSD-3-Clause +# Copyright 2026 Intel Corporation +# +# apply-ffmpeg-12bit-patch.sh — Apply the 12-bit pixel format patch to +# the FFmpeg mtl_st20p TX plugin, then rebuild and install FFmpeg. +# +# Usage: +# ./scripts/apply-ffmpeg-12bit-patch.sh [FFMPEG_SRC_DIR] +# +# FFMPEG_SRC_DIR defaults to ~/ffmpeg_build/FFmpeg if not specified. +# The MTL plugin source (mtl_st20p_tx.c) must already be copied into +# FFmpeg's libavdevice/ directory before running this script. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PATCH_FILE="$SCRIPT_DIR/../patches/ffmpeg-mtl-st20p-12bit-formats.patch" +FFMPEG_DIR="${1:-$HOME/ffmpeg_build/FFmpeg}" + +if [[ ! -f "$PATCH_FILE" ]]; then + echo "ERROR: Patch file not found: $PATCH_FILE" + exit 1 +fi + +if [[ ! -d "$FFMPEG_DIR/libavdevice" ]]; then + echo "ERROR: FFmpeg source not found at: $FFMPEG_DIR" + echo "Usage: $0 [FFMPEG_SRC_DIR]" + exit 1 +fi + +TARGET="$FFMPEG_DIR/libavdevice/mtl_st20p_tx.c" +if [[ ! -f "$TARGET" ]]; then + echo "ERROR: mtl_st20p_tx.c not found in $FFMPEG_DIR/libavdevice/" + echo "Ensure the MTL FFmpeg plugin has been copied into the FFmpeg source tree." + exit 1 +fi + +# Check if patch is already applied +if grep -q "AV_PIX_FMT_YUV444P12LE" "$TARGET"; then + echo "Patch already applied — skipping." + exit 0 +fi + +echo "Applying 12-bit format patch to: $TARGET" +cd "$FFMPEG_DIR" +patch -p1 < "$PATCH_FILE" + +echo "" +echo "Patch applied successfully." +echo "Rebuild FFmpeg with:" +echo " cd $FFMPEG_DIR && make -j\$(nproc) && sudo make install && sudo ldconfig" From e34513be7b667829a04a5a0cbfe68d5479cd2d11 Mon Sep 17 00:00:00 2001 From: roshan-ku Date: Tue, 19 May 2026 14:54:38 +0530 Subject: [PATCH 04/10] Update README with 12-bit format support and FFmpeg patch instructions - Add 12-bit formats to Supported Formats table - Update config fmt field to reference the formats section - Add 'FFmpeg MTL Plugin Patch' section with apply instructions --- README.md | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 33028a8..2831575 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ dvledtx uses a JSON config file with three sections: | **video** | `width` | Frame width in pixels | | | `height` | Frame height in pixels | | | `fps` | Frames per second (25, 30, 50, 60) | -| | `fmt` | Pixel format (`yuv422p10le`, `yuv420`, `yuv444p10le`, `gbrp10le`) | +| | `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) | @@ -158,10 +158,17 @@ When `log_file` is set, log output is written to that file in addition to the co ## Supported Formats ### Video Formats -- **yuv422p10le**: YUV 4:2:2 10-bit little endian (default) -- **yuv420**: YUV 4:2:0 8-bit -- **yuv444p10le**: YUV 4:4:4 10-bit little endian -- **gbrp10le**: RGB (GBR planar) 10-bit little endian + +| Format | Chroma | Bit Depth | Color Space | +|--------|--------|-----------|-------------| +| `yuv422p10le` | 4:2:2 | 10-bit | YUV (default) | +| `yuv420` | 4:2:0 | 8-bit | YUV | +| `yuv444p10le` | 4:4:4 | 10-bit | YUV | +| `gbrp10le` | 4:4:4 | 10-bit | RGB | +| `yuv422p12le` | 4:2:2 | 12-bit | YUV | +| `yuv420p12le` | 4:2:0 | 12-bit | YUV | +| `yuv444p12le` | 4:4:4 | 12-bit | YUV | +| `gbrp12le` | 4:4:4 | 12-bit | RGB | ### Frame Rates - 25 fps @@ -180,6 +187,25 @@ When `log_file` is set, log output is written to that file in addition to the co - **Hardware Acceleration**: Uses MTL's hardware features - **Efficient Threading**: Minimal context switching +### FFmpeg MTL Plugin Patch (required for 12-bit formats) + +The upstream FFmpeg `mtl_st20p` muxer plugin only supports `yuv422p10le`, `y210le`, and `rgb24`. To enable all formats supported by dvledtx (including 12-bit), apply the included patch after building FFmpeg with the MTL plugin: + +```bash +./scripts/apply-ffmpeg-12bit-patch.sh [FFMPEG_SRC_DIR] +``` + +`FFMPEG_SRC_DIR` defaults to `~/ffmpeg_build/FFmpeg`. Then rebuild and install FFmpeg: + +```bash +cd ~/ffmpeg_build/FFmpeg +make -j$(nproc) +sudo make install +sudo ldconfig +``` + +The script is idempotent — it skips if the patch is already applied. + ### Running Unit Tests Install dependencies if not present From dbfc866a6046e5739687262f14fd9c41111f08bd Mon Sep 17 00:00:00 2001 From: roshan-ku Date: Mon, 1 Jun 2026 15:21:21 +0530 Subject: [PATCH 05/10] removed patch files --- config/tx_1session_12bit.json | 24 --------- patches/ffmpeg-mtl-st20p-12bit-formats.patch | 37 -------------- scripts/apply-ffmpeg-12bit-patch.sh | 52 -------------------- 3 files changed, 113 deletions(-) delete mode 100644 config/tx_1session_12bit.json delete mode 100644 patches/ffmpeg-mtl-st20p-12bit-formats.patch delete mode 100755 scripts/apply-ffmpeg-12bit-patch.sh diff --git a/config/tx_1session_12bit.json b/config/tx_1session_12bit.json deleted file mode 100644 index 55046cc..0000000 --- a/config/tx_1session_12bit.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "log_file": "dvledtx_12bit.log", - "interfaces": [ - { - "name": "0000:06:00.0", - "sip": "192.168.50.29", - "dip": "239.168.85.20" - } - ], - "video": { - "width": 1920, - "height": 1080, - "fps": 30, - "fmt": "yuv444p12le", - "tx_url": "test_12bit_yuv444p12le.mkv" - }, - "tx_sessions": [ - { - "udp_port": 20000, - "payload_type": 96, - "crop": { "x": 0, "y": 0, "w": 1920, "h": 1080 } - } - ] -} diff --git a/patches/ffmpeg-mtl-st20p-12bit-formats.patch b/patches/ffmpeg-mtl-st20p-12bit-formats.patch deleted file mode 100644 index 725eb00..0000000 --- a/patches/ffmpeg-mtl-st20p-12bit-formats.patch +++ /dev/null @@ -1,37 +0,0 @@ ---- a/libavdevice/mtl_st20p_tx.c -+++ b/libavdevice/mtl_st20p_tx.c -@@ -106,6 +106,34 @@ - ops_tx.input_fmt = ST_FRAME_FMT_RGB8; - ops_tx.transport_fmt = ST20_FMT_RGB_8BIT; - break; -+ case AV_PIX_FMT_YUV420P: -+ ops_tx.input_fmt = ST_FRAME_FMT_YUV420CUSTOM8; -+ ops_tx.transport_fmt = ST20_FMT_YUV_420_8BIT; -+ break; -+ case AV_PIX_FMT_YUV444P10LE: -+ ops_tx.input_fmt = ST_FRAME_FMT_YUV444PLANAR10LE; -+ ops_tx.transport_fmt = ST20_FMT_YUV_444_10BIT; -+ break; -+ case AV_PIX_FMT_GBRP10LE: -+ ops_tx.input_fmt = ST_FRAME_FMT_GBRPLANAR10LE; -+ ops_tx.transport_fmt = ST20_FMT_RGB_10BIT; -+ break; -+ case AV_PIX_FMT_YUV422P12LE: -+ ops_tx.input_fmt = ST_FRAME_FMT_YUV422PLANAR12LE; -+ ops_tx.transport_fmt = ST20_FMT_YUV_422_12BIT; -+ break; -+ case AV_PIX_FMT_YUV420P12LE: -+ ops_tx.input_fmt = ST_FRAME_FMT_YUV420PLANAR8; /* no dedicated 12-bit 420 frame fmt */ -+ ops_tx.transport_fmt = ST20_FMT_YUV_420_12BIT; -+ break; -+ case AV_PIX_FMT_YUV444P12LE: -+ ops_tx.input_fmt = ST_FRAME_FMT_YUV444PLANAR12LE; -+ ops_tx.transport_fmt = ST20_FMT_YUV_444_12BIT; -+ break; -+ case AV_PIX_FMT_GBRP12LE: -+ ops_tx.input_fmt = ST_FRAME_FMT_GBRPLANAR12LE; -+ ops_tx.transport_fmt = ST20_FMT_RGB_12BIT; -+ break; - default: - err(ctx, "%s, unsupported pixel format: %d\n", __func__, s->pixel_format); - return AVERROR(EINVAL); diff --git a/scripts/apply-ffmpeg-12bit-patch.sh b/scripts/apply-ffmpeg-12bit-patch.sh deleted file mode 100755 index 599d0d9..0000000 --- a/scripts/apply-ffmpeg-12bit-patch.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2026 Intel Corporation -# -# apply-ffmpeg-12bit-patch.sh — Apply the 12-bit pixel format patch to -# the FFmpeg mtl_st20p TX plugin, then rebuild and install FFmpeg. -# -# Usage: -# ./scripts/apply-ffmpeg-12bit-patch.sh [FFMPEG_SRC_DIR] -# -# FFMPEG_SRC_DIR defaults to ~/ffmpeg_build/FFmpeg if not specified. -# The MTL plugin source (mtl_st20p_tx.c) must already be copied into -# FFmpeg's libavdevice/ directory before running this script. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PATCH_FILE="$SCRIPT_DIR/../patches/ffmpeg-mtl-st20p-12bit-formats.patch" -FFMPEG_DIR="${1:-$HOME/ffmpeg_build/FFmpeg}" - -if [[ ! -f "$PATCH_FILE" ]]; then - echo "ERROR: Patch file not found: $PATCH_FILE" - exit 1 -fi - -if [[ ! -d "$FFMPEG_DIR/libavdevice" ]]; then - echo "ERROR: FFmpeg source not found at: $FFMPEG_DIR" - echo "Usage: $0 [FFMPEG_SRC_DIR]" - exit 1 -fi - -TARGET="$FFMPEG_DIR/libavdevice/mtl_st20p_tx.c" -if [[ ! -f "$TARGET" ]]; then - echo "ERROR: mtl_st20p_tx.c not found in $FFMPEG_DIR/libavdevice/" - echo "Ensure the MTL FFmpeg plugin has been copied into the FFmpeg source tree." - exit 1 -fi - -# Check if patch is already applied -if grep -q "AV_PIX_FMT_YUV444P12LE" "$TARGET"; then - echo "Patch already applied — skipping." - exit 0 -fi - -echo "Applying 12-bit format patch to: $TARGET" -cd "$FFMPEG_DIR" -patch -p1 < "$PATCH_FILE" - -echo "" -echo "Patch applied successfully." -echo "Rebuild FFmpeg with:" -echo " cd $FFMPEG_DIR && make -j\$(nproc) && sudo make install && sudo ldconfig" From 42dbb5b24b55252aed957681cf5e9ac640e05d0a Mon Sep 17 00:00:00 2001 From: roshan-ku Date: Tue, 2 Jun 2026 11:13:36 +0530 Subject: [PATCH 06/10] Remove yuv420p12le format (unsupported by MTL) and FFmpeg patch section - Remove yuv420p12le from transport format mapping, config validation, and format assignment - Remove associated unit test for yuv420p12le - Remove 'FFmpeg MTL Plugin Patch' section from README (no patches provided) - Update supported formats documentation --- README.md | 20 -------------------- src/mtl/mtl_tx.c | 1 - src/util/config_reader.c | 4 +--- tests/test_config_reader.c | 2 +- tests/test_mtl_tx.c | 8 -------- 5 files changed, 2 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 2831575..77c8f83 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,6 @@ When `log_file` is set, log output is written to that file in addition to the co | `yuv444p10le` | 4:4:4 | 10-bit | YUV | | `gbrp10le` | 4:4:4 | 10-bit | RGB | | `yuv422p12le` | 4:2:2 | 12-bit | YUV | -| `yuv420p12le` | 4:2:0 | 12-bit | YUV | | `yuv444p12le` | 4:4:4 | 12-bit | YUV | | `gbrp12le` | 4:4:4 | 12-bit | RGB | @@ -187,25 +186,6 @@ When `log_file` is set, log output is written to that file in addition to the co - **Hardware Acceleration**: Uses MTL's hardware features - **Efficient Threading**: Minimal context switching -### FFmpeg MTL Plugin Patch (required for 12-bit formats) - -The upstream FFmpeg `mtl_st20p` muxer plugin only supports `yuv422p10le`, `y210le`, and `rgb24`. To enable all formats supported by dvledtx (including 12-bit), apply the included patch after building FFmpeg with the MTL plugin: - -```bash -./scripts/apply-ffmpeg-12bit-patch.sh [FFMPEG_SRC_DIR] -``` - -`FFMPEG_SRC_DIR` defaults to `~/ffmpeg_build/FFmpeg`. Then rebuild and install FFmpeg: - -```bash -cd ~/ffmpeg_build/FFmpeg -make -j$(nproc) -sudo make install -sudo ldconfig -``` - -The script is idempotent — it skips if the patch is already applied. - ### Running Unit Tests Install dependencies if not present diff --git a/src/mtl/mtl_tx.c b/src/mtl/mtl_tx.c index 88c82fd..51a4aca 100644 --- a/src/mtl/mtl_tx.c +++ b/src/mtl/mtl_tx.c @@ -66,7 +66,6 @@ enum st20_fmt get_transport_format(enum AVPixelFormat fmt) { case AV_PIX_FMT_YUV444P10LE: return ST20_FMT_YUV_444_10BIT; case AV_PIX_FMT_GBRP10LE: return ST20_FMT_RGB_10BIT; case AV_PIX_FMT_YUV422P12LE: return ST20_FMT_YUV_422_12BIT; - case AV_PIX_FMT_YUV420P12LE: return ST20_FMT_YUV_420_12BIT; case AV_PIX_FMT_YUV444P12LE: return ST20_FMT_YUV_444_12BIT; case AV_PIX_FMT_GBRP12LE: return ST20_FMT_RGB_12BIT; default: diff --git a/src/util/config_reader.c b/src/util/config_reader.c index 488a476..0c835f2 100644 --- a/src/util/config_reader.c +++ b/src/util/config_reader.c @@ -441,12 +441,11 @@ int validate_tx_config(const struct dvledtx_config* config) { strcmp(config->fmt, "yuv444p10le") != 0 && strcmp(config->fmt, "gbrp10le") != 0 && strcmp(config->fmt, "yuv422p12le") != 0 && - strcmp(config->fmt, "yuv420p12le") != 0 && strcmp(config->fmt, "yuv444p12le") != 0 && strcmp(config->fmt, "gbrp12le") != 0) { LOG_ERROR("unsupported pixel format '%s'", config->fmt); LOG_ERROR(" Supported: yuv422p10le, yuv420, yuv444p10le, gbrp10le, " - "yuv422p12le, yuv420p12le, yuv444p12le, gbrp12le"); + "yuv422p12le, yuv444p12le, gbrp12le"); return -1; } @@ -597,7 +596,6 @@ int load_and_apply_config(struct dvledtx_context* app, const char* config_file) else if (strcmp(config.fmt, "yuv444p10le") == 0) app->fmt = AV_PIX_FMT_YUV444P10LE; else if (strcmp(config.fmt, "gbrp10le") == 0) app->fmt = AV_PIX_FMT_GBRP10LE; else if (strcmp(config.fmt, "yuv422p12le") == 0) app->fmt = AV_PIX_FMT_YUV422P12LE; - else if (strcmp(config.fmt, "yuv420p12le") == 0) app->fmt = AV_PIX_FMT_YUV420P12LE; else if (strcmp(config.fmt, "yuv444p12le") == 0) app->fmt = AV_PIX_FMT_YUV444P12LE; else if (strcmp(config.fmt, "gbrp12le") == 0) app->fmt = AV_PIX_FMT_GBRP12LE; else { diff --git a/tests/test_config_reader.c b/tests/test_config_reader.c index d9c415e..8bea230 100644 --- a/tests/test_config_reader.c +++ b/tests/test_config_reader.c @@ -354,7 +354,7 @@ static void test_validate_all_supported_fmts_pass(void **state) { (void)state; const char *fmts[] = {"yuv422p10le", "yuv420", "yuv444p10le", "gbrp10le", - "yuv422p12le", "yuv420p12le", "yuv444p12le", "gbrp12le"}; + "yuv422p12le", "yuv444p12le", "gbrp12le"}; for (size_t i = 0; i < sizeof(fmts) / sizeof(fmts[0]); i++) { struct dvledtx_config cfg; fill_valid_config(&cfg); diff --git a/tests/test_mtl_tx.c b/tests/test_mtl_tx.c index 0f60d7c..8aee999 100644 --- a/tests/test_mtl_tx.c +++ b/tests/test_mtl_tx.c @@ -78,13 +78,6 @@ static void test_get_transport_format_yuv422p12le(void **state) ST20_FMT_YUV_422_12BIT); } -static void test_get_transport_format_yuv420p12le(void **state) -{ - (void)state; - assert_int_equal(get_transport_format(AV_PIX_FMT_YUV420P12LE), - ST20_FMT_YUV_420_12BIT); -} - static void test_get_transport_format_yuv444p12le(void **state) { (void)state; @@ -399,7 +392,6 @@ int main(void) cmocka_unit_test(test_get_transport_format_yuv444p10le), cmocka_unit_test(test_get_transport_format_gbrp10le), cmocka_unit_test(test_get_transport_format_yuv422p12le), - cmocka_unit_test(test_get_transport_format_yuv420p12le), cmocka_unit_test(test_get_transport_format_yuv444p12le), cmocka_unit_test(test_get_transport_format_gbrp12le), cmocka_unit_test(test_get_transport_format_unknown_returns_error), From 8142f719bc1e5f0f2f1c0f34de23dc78aaf41a59 Mon Sep 17 00:00:00 2001 From: roshan-ku Date: Tue, 2 Jun 2026 11:15:34 +0530 Subject: [PATCH 07/10] Remove default label from yuv422p10le in pixel format table --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 77c8f83..3e6526a 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ When `log_file` is set, log output is written to that file in addition to the co | Format | Chroma | Bit Depth | Color Space | |--------|--------|-----------|-------------| -| `yuv422p10le` | 4:2:2 | 10-bit | YUV (default) | +| `yuv422p10le` | 4:2:2 | 10-bit | YUV | | `yuv420` | 4:2:0 | 8-bit | YUV | | `yuv444p10le` | 4:4:4 | 10-bit | YUV | | `gbrp10le` | 4:4:4 | 10-bit | RGB | From 0f6e37372f007dbd13ec299639226e4850579f20 Mon Sep 17 00:00:00 2001 From: roshan-ku Date: Wed, 3 Jun 2026 11:19:13 +0530 Subject: [PATCH 08/10] Fix cppcheck constVariablePointer warnings - Make 'frame' pointer const in st20p_tx_thread (session_manager.c) - Update ffmpeg_tx_send_yuv_frame to accept const AVFrame* src - Make 'app' pointer const in open_shared_ffmpeg (ffmpeg_decoder.c) - Update test mock signature to match --- src/core/session_manager.c | 2 +- src/ffmpeg/ffmpeg_decoder.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/session_manager.c b/src/core/session_manager.c index f2eb8b6..0c90f86 100644 --- a/src/core/session_manager.c +++ b/src/core/session_manager.c @@ -173,7 +173,7 @@ static void* st20p_tx_thread(void* arg) { int crop_h = ctx->crop_height > 0 ? ctx->crop_height : (int)ctx->app->height; while (ctx->app->exit == false && session_manager_should_exit() == false) { - AVFrame* frame = tx_fetch_next_frame(ctx); + const AVFrame* frame = tx_fetch_next_frame(ctx); if (frame == NULL) { if (ctx->app->exit == false && session_manager_should_exit() == false) LOG_ERROR("ST20P TX(%d): tx_fetch_next_frame failed", ctx->idx); diff --git a/src/ffmpeg/ffmpeg_decoder.c b/src/ffmpeg/ffmpeg_decoder.c index 0d2395b..7cee9dc 100644 --- a/src/ffmpeg/ffmpeg_decoder.c +++ b/src/ffmpeg/ffmpeg_decoder.c @@ -320,7 +320,7 @@ static void close_ffmpeg_decoder( * Open/close shared FFmpeg decoder (multi-session input path) * ========================================================================= */ int open_shared_ffmpeg(struct shared_decode_ctx* dec, const char* filename) { - struct dvledtx_context* app = dec->app; + const struct dvledtx_context* app = dec->app; return open_ffmpeg_decoder( filename, "Shared decode", app->fmt, (int)app->width, (int)app->height, From fa365d2b2e2762975f34d830cef5a464ef5e9757 Mon Sep 17 00:00:00 2001 From: roshan-ku Date: Wed, 3 Jun 2026 11:31:02 +0530 Subject: [PATCH 09/10] test: add get_input_format tests for 12-bit formats Register test_get_input_format_yuv422p12le, test_get_input_format_yuv444p12le, and test_get_input_format_gbrp12le in the test runner to ensure coverage of the 12-bit input format mapping paths. Addresses Copilot review comment on PR #26. --- tests/test_mtl_tx.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/test_mtl_tx.c b/tests/test_mtl_tx.c index 8aee999..71d64dd 100644 --- a/tests/test_mtl_tx.c +++ b/tests/test_mtl_tx.c @@ -99,6 +99,31 @@ static void test_get_transport_format_unknown_returns_error(void **state) assert_int_equal((int)get_transport_format(AV_PIX_FMT_NONE), -1); } +/* ========================================================================= + * get_input_format — 12-bit formats + * ========================================================================= */ + +static void test_get_input_format_yuv422p12le(void **state) +{ + (void)state; + assert_int_equal(get_input_format(AV_PIX_FMT_YUV422P12LE), + ST_FRAME_FMT_YUV422PLANAR12LE); +} + +static void test_get_input_format_yuv444p12le(void **state) +{ + (void)state; + assert_int_equal(get_input_format(AV_PIX_FMT_YUV444P12LE), + ST_FRAME_FMT_YUV444PLANAR12LE); +} + +static void test_get_input_format_gbrp12le(void **state) +{ + (void)state; + assert_int_equal(get_input_format(AV_PIX_FMT_GBRP12LE), + ST_FRAME_FMT_GBRPLANAR12LE); +} + /* ========================================================================= * get_st_fps * ========================================================================= */ @@ -396,6 +421,11 @@ int main(void) cmocka_unit_test(test_get_transport_format_gbrp12le), cmocka_unit_test(test_get_transport_format_unknown_returns_error), + /* get_input_format — 12-bit */ + cmocka_unit_test(test_get_input_format_yuv422p12le), + cmocka_unit_test(test_get_input_format_yuv444p12le), + cmocka_unit_test(test_get_input_format_gbrp12le), + /* get_st_fps */ cmocka_unit_test(test_get_st_fps_25), cmocka_unit_test(test_get_st_fps_30), From f278526d601972e0784358f8163915fc87d11793 Mon Sep 17 00:00:00 2001 From: roshan-ku Date: Thu, 4 Jun 2026 11:39:35 +0530 Subject: [PATCH 10/10] fix: add const qualifier to AVFrame* params in mtl_tx to silence -Wdiscarded-qualifiers --- include/mtl/mtl_tx.h | 4 ++-- src/mtl/mtl_tx.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/mtl/mtl_tx.h b/include/mtl/mtl_tx.h index c996061..7c5756a 100644 --- a/include/mtl/mtl_tx.h +++ b/include/mtl/mtl_tx.h @@ -69,7 +69,7 @@ enum st_fps get_st_fps(int fps); * Handles all planar YUV formats (4:2:0, 4:2:2, 4:4:4) and GBRP at any * supported bit depth. */ -void mtl_copy_crop_to_frame(struct st_frame* dst, AVFrame* src, +void mtl_copy_crop_to_frame(struct st_frame* dst, const AVFrame* src, int crop_x, int crop_y, int crop_w, int crop_h, enum AVPixelFormat fmt); @@ -121,7 +121,7 @@ void mtl_tx_session_free(struct st20p_tx_ctx* ctx); * * Returns 0 on success, -1 on error. */ -int mtl_tx_send_yuv_frame(struct st20p_tx_ctx* ctx, AVFrame* src, +int mtl_tx_send_yuv_frame(struct st20p_tx_ctx* ctx, const AVFrame* src, int crop_x, int crop_y, int crop_w, int crop_h); /* diff --git a/src/mtl/mtl_tx.c b/src/mtl/mtl_tx.c index 01ab6f4..fdff0a7 100644 --- a/src/mtl/mtl_tx.c +++ b/src/mtl/mtl_tx.c @@ -113,7 +113,7 @@ enum st_fps get_st_fps(int fps) { * crop_x/y — top-left corner in the full-width shared yuv_frame (luma coords) * crop_w/h — crop rectangle dimensions in luma pixels */ -void mtl_copy_crop_to_frame(struct st_frame* dst, AVFrame* src, +void mtl_copy_crop_to_frame(struct st_frame* dst, const AVFrame* src, int crop_x, int crop_y, int crop_w, int crop_h, enum AVPixelFormat fmt) { @@ -302,7 +302,7 @@ void mtl_tx_session_free(struct st20p_tx_ctx* ctx) { * Returns 0 on success, -1 on error (e.g. get_frame returned NULL after * timeout, or copy failed). */ -int mtl_tx_send_yuv_frame(struct st20p_tx_ctx* ctx, AVFrame* src, +int mtl_tx_send_yuv_frame(struct st20p_tx_ctx* ctx, const AVFrame* src, int crop_x, int crop_y, int crop_w, int crop_h) { if (!ctx->handle || !src) return -1;