diff --git a/README.md b/README.md index 623cab9..a62771f 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,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) | @@ -209,10 +209,16 @@ 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 | +| `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 | +| `yuv444p12le` | 4:4:4 | 12-bit | YUV | +| `gbrp12le` | 4:4:4 | 12-bit | RGB | ### Frame Rates - 25 fps 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/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, diff --git a/src/mtl/mtl_tx.c b/src/mtl/mtl_tx.c index 5c4e3e9..fdff0a7 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,9 @@ 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_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; @@ -107,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) { @@ -296,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; diff --git a/src/util/config_reader.c b/src/util/config_reader.c index ece4d6a..09b6841 100644 --- a/src/util/config_reader.c +++ b/src/util/config_reader.c @@ -439,9 +439,13 @@ 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, "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, yuv444p12le, gbrp12le"); return -1; } @@ -591,6 +595,9 @@ 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, "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..8bea230 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", "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..71d64dd 100644 --- a/tests/test_mtl_tx.c +++ b/tests/test_mtl_tx.c @@ -71,6 +71,27 @@ 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_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; @@ -78,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 * ========================================================================= */ @@ -370,8 +416,16 @@ 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_yuv444p12le), + 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),