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;