diff --git a/libavfilter/vf_cuda_grid.c b/libavfilter/vf_cuda_grid.c index aaa2a31..c9e0a4a 100644 --- a/libavfilter/vf_cuda_grid.c +++ b/libavfilter/vf_cuda_grid.c @@ -244,7 +244,7 @@ typedef struct CudaGridContext { int max_cells; /* Phase 7: число input pads creates в init() */ int aspect_mode; /* 0=stretch, 1=fit (letterbox/pillarbox) */ int placeholder_timeout_ms; /* Stale input → blit placeholder. 0=disable */ - char *placeholder_icon; /* icon name (resolved via icon_dir) для placeholder */ + char *placeholder_icon; /* icon base name (controller generates "_.png" per-cell) */ /* Per-input stale detection (wall-clock based) */ int64_t last_frame_pts[MAX_CELLS]; @@ -1363,10 +1363,18 @@ static int cuda_grid_compose(FFFrameSync *fs) DIV_UP(half_cw, BLOCKX), DIV_UP(half_ch, BLOCKY), 1, BLOCKX, BLOCKY, 1, 0, s->cu_stream, args, NULL)); } - /* Blit placeholder centered (если loaded). Используем существующий - * icon atlas machinery — placeholder = icon с именем s->placeholder_icon. */ + /* Per-cell placeholder сначала ("_"), fallback к generic. + * Иconки: offline_0.png = "ПАРКИНГ", offline_1.png = "ПЕРЕДНИЙ ДВОР", etc. + * Если per-cell не нашли → загружаем generic с именем s->placeholder_icon. */ if (s->placeholder_icon && s->placeholder_icon[0]) { - if (ensure_icon_atlas(ctx, s->placeholder_icon, &pa) == 0 && pa) { + char per_cell_name[64]; + snprintf(per_cell_name, sizeof(per_cell_name), "%s_%d", + s->placeholder_icon, pad); + if (ensure_icon_atlas(ctx, per_cell_name, &pa) != 0 || !pa) { + pa = NULL; + ensure_icon_atlas(ctx, s->placeholder_icon, &pa); + } + if (pa) { int px = cx + (cw - pa->w) / 2; int py = cy + (ch - pa->h) / 2; px &= ~1; py &= ~1; @@ -1945,7 +1953,7 @@ static const AVOption cuda_grid_options[] = { OFFSET(aspect_mode), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, FLAGS }, { "placeholder_timeout_ms", "если input pad не дал new frame N ms — blit placeholder (default 2000, 0=disable)", OFFSET(placeholder_timeout_ms), AV_OPT_TYPE_INT, { .i64 = 2000 }, 0, 60000, FLAGS }, - { "placeholder_icon", "icon name (через icon_dir) для placeholder при stale input. Default 'offline'", + { "placeholder_icon", "icon base name (controller рендерит _.png per-cell с labels; fallback .png)", OFFSET(placeholder_icon), AV_OPT_TYPE_STRING, { .str = "offline" }, 0, 0, FLAGS }, { NULL } }; diff --git a/libavformat/cuframesdec.c b/libavformat/cuframesdec.c index b5da5ea..aa7801e 100644 --- a/libavformat/cuframesdec.c +++ b/libavformat/cuframesdec.c @@ -20,6 +20,7 @@ #include "libavutil/avstring.h" #include "libavutil/imgutils.h" #include "libavutil/opt.h" +#include "libavutil/time.h" #include #include @@ -42,6 +43,12 @@ typedef struct CuframesDemuxerContext { * сохраняем его для первого read_packet */ int pending_first_frame; cuframes_frame_t *first_frame; + /* Reconnect state — publisher container restart = new IPC namespace, + * old subscriber становится DISCONNECTED. Без reconnect логики input + * pad навсегда EOF → filter показывает placeholder forever даже когда + * publisher восстановился. */ + char saved_key[80]; + int64_t last_reconnect_us; } CuframesDemuxerContext; #define OFFSET(x) offsetof(CuframesDemuxerContext, x) @@ -133,6 +140,8 @@ static int cuframes_read_header(AVFormatContext *s) key, cuframes_strerror(rc)); return AVERROR_EXTERNAL; } + /* Save key для reconnect attempts. */ + av_strlcpy(c->saved_key, key, sizeof(c->saved_key)); /* Получаем первый кадр чтобы узнать width/height. */ cuframes_frame_t *frame = NULL; @@ -175,6 +184,34 @@ static int cuframes_read_header(AVFormatContext *s) return 0; } +/* Attempts re-subscribe after publisher disconnect. Rate-limited к 1 try / 2 sec. */ +static void try_reconnect(AVFormatContext *s) +{ + CuframesDemuxerContext *c = s->priv_data; + int64_t now = av_gettime(); + if (now - c->last_reconnect_us < 2000000) return; + c->last_reconnect_us = now; + + if (c->sub) { + cuframes_subscriber_destroy(c->sub); + c->sub = NULL; + } + cuframes_subscriber_config_t rcfg = {0}; + rcfg.key = c->saved_key; + rcfg.consumer_name = NULL; + rcfg.mode = CUFRAMES_MODE_NEWEST_ONLY; + rcfg.cuda_device = c->cuda_device; + rcfg.connect_timeout_ms = 1000; + int rrc = cuframes_subscriber_create(&rcfg, &c->sub); + if (rrc == CUFRAMES_OK) { + av_log(s, AV_LOG_INFO, "cuframes: reconnected to '%s'\n", c->saved_key); + } else { + c->sub = NULL; + av_log(s, AV_LOG_DEBUG, "cuframes: reconnect '%s' fail: %s\n", + c->saved_key, cuframes_strerror(rrc)); + } +} + static int cuframes_read_packet(AVFormatContext *s, AVPacket *pkt) { CuframesDemuxerContext *c = s->priv_data; @@ -185,11 +222,42 @@ static int cuframes_read_packet(AVFormatContext *s, AVPacket *pkt) c->first_frame = NULL; c->pending_first_frame = 0; } else { + /* Guard — subscriber может быть NULL после failed reconnect attempt */ + if (!c->sub) { + try_reconnect(s); + return AVERROR(EAGAIN); + } int rc = cuframes_subscriber_next(c->sub, c->cuda_stream, &frame, 5000); if (rc == CUFRAMES_ERR_TIMEOUT || rc == CUFRAMES_ERR_WOULD_BLOCK) return AVERROR(EAGAIN); - if (rc == CUFRAMES_ERR_DISCONNECTED) - return AVERROR_EOF; + if (rc == CUFRAMES_ERR_DISCONNECTED) { + /* Publisher container died / recreated. Try reconnect — rate-limited + * к одному attempt каждые 2 sec чтобы не spam'ить unix socket. + * Возвращаем EAGAIN (ffmpeg retries) вместо EOF (ffmpeg stops). */ + int64_t now = av_gettime(); + if (now - c->last_reconnect_us > 2000000) { + c->last_reconnect_us = now; + if (c->sub) { + cuframes_subscriber_destroy(c->sub); + c->sub = NULL; + } + cuframes_subscriber_config_t rcfg = {0}; + rcfg.key = c->saved_key; + rcfg.consumer_name = NULL; + rcfg.mode = CUFRAMES_MODE_NEWEST_ONLY; + rcfg.cuda_device = c->cuda_device; + rcfg.connect_timeout_ms = 1000; + int rrc = cuframes_subscriber_create(&rcfg, &c->sub); + if (rrc == CUFRAMES_OK) { + av_log(s, AV_LOG_INFO, "cuframes: reconnected to '%s'\n", + c->saved_key); + } else { + av_log(s, AV_LOG_DEBUG, "cuframes: reconnect к '%s' fail: %s\n", + c->saved_key, cuframes_strerror(rrc)); + } + } + return AVERROR(EAGAIN); + } if (rc != CUFRAMES_OK || !frame) { av_log(s, AV_LOG_ERROR, "cuframes: next: %s\n", cuframes_strerror(rc)); return AVERROR_EXTERNAL;