ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
renderer.c File Reference

render_file_* — ties together font resolution, platform renderer, FFmpeg encoder More...

Go to the source code of this file.

Data Structures

struct  render_file_ctx_s
 

Functions

asciichat_error_t render_file_create (const char *output_path, int cols, int rows, int fps, int theme, render_file_ctx_t **out)
 
void render_file_set_live_timing (render_file_ctx_t *ctx)
 
asciichat_error_t render_file_write_frame (render_file_ctx_t *ctx, const char *ansi_frame, uint64_t captured_ns)
 
void render_file_set_snapshot_actual_duration (render_file_ctx_t *ctx, double actual_duration_sec)
 
asciichat_error_t render_file_destroy (render_file_ctx_t *ctx)
 

Detailed Description

render_file_* — ties together font resolution, platform renderer, FFmpeg encoder

Definition in file renderer.c.

Function Documentation

◆ render_file_create()

asciichat_error_t render_file_create ( const char *  output_path,
int  cols,
int  rows,
int  fps,
int  theme,
render_file_ctx_t **  out 
)

Definition at line 33 of file renderer.c.

34 {
35 log_info("[RENDER_FILE_CREATE] CALLED with output_path=%s, cols=%d, rows=%d, fps=%d, theme=%d, out=%p",
36 output_path ? output_path : "(null)", cols, rows, fps, theme, (void *)out);
37 render_file_ctx_t *ctx = SAFE_CALLOC(1, sizeof(*ctx), render_file_ctx_t *);
38
39 // Resolve font spec → platform-appropriate file path, family name, or bundled data.
40 char font_spec[512] = {0};
41 bool font_is_path = false;
42 const uint8_t *font_data = NULL;
43 size_t font_data_size = 0;
44 const char *raw_font = GET_OPTION(render_font);
45
46 // Log the font option value from GET_OPTION (for WASM font debugging)
47 log_info("[WASM_FONT_GETTER] GET_OPTION(render_font) returned: '%s' (empty=%d), GET_OPTION(matrix_rain)=%d",
48 raw_font ? raw_font : "(null)", (!raw_font || raw_font[0] == '\0') ? 1 : 0, GET_OPTION(matrix_rain));
49
50 // Auto-select matrix font when --matrix flag is set, unless user explicitly overrides with --render-font
51 log_debug("raw_font=%s, matrix_rain=%d", raw_font, GET_OPTION(matrix_rain));
52 if ((!raw_font || raw_font[0] == '\0') && GET_OPTION(matrix_rain)) {
53 raw_font = "matrix";
54 log_debug("render_file_create: [MATRIX] Using matrix font (enabled by --matrix flag)");
55 }
56
58 platform_font_resolve(raw_font, font_spec, sizeof(font_spec), &font_is_path, &font_data, &font_data_size);
59 if (fe != ASCIICHAT_OK) {
60 log_warn("renderer: font resolution failed for '%s' — using bundled default",
61 raw_font ? raw_font : "(explicit system)");
62 fe = platform_font_resolve("default", font_spec, sizeof(font_spec), &font_is_path, &font_data, &font_data_size);
63 if (fe != ASCIICHAT_OK) {
64 SAFE_FREE(ctx);
65 return fe;
66 }
67 }
68
69 log_debug("render_file_create: Font resolved: font_spec='%s', font_is_path=%d, font_data=%p (size=%zu)", font_spec,
70 font_is_path, (void *)font_data, font_data_size);
71
72 // Use reasonable default font size for render-file quality
73 double font_size_pt = GET_OPTION(render_font_size);
74 term_renderer_theme_t resolved_theme = (term_renderer_theme_t)theme;
75 if (resolved_theme == TERM_RENDERER_THEME_AUTO) {
77 log_debug("render_file_create: auto theme resolved to %s",
78 resolved_theme == TERM_RENDERER_THEME_DARK ? "dark" : "light");
79 }
80
81 term_renderer_config_t tr_cfg = {
82 .cols = cols,
83 .rows = rows,
84 .font_size_pt = font_size_pt,
85 .theme = resolved_theme,
86 .font_is_path = font_is_path,
87 .font_data = font_data,
88 .font_data_size = font_data_size,
89 };
90 SAFE_STRNCPY(tr_cfg.font_spec, font_spec, sizeof(tr_cfg.font_spec));
91
93 if (err != ASCIICHAT_OK) {
94 log_error("render_file_create: term_renderer_create failed: %s", asciichat_error_string(err));
95 SAFE_FREE(ctx);
96 return err;
97 }
98 int actual_width_px = term_renderer_width_px(ctx->renderer);
99 int actual_height_px = term_renderer_height_px(ctx->renderer);
100 log_info("render_file_create: GRID DIMENSIONS: %ux%u cells -> PIXEL DIMENSIONS: %dx%d px", cols, rows,
101 actual_width_px, actual_height_px);
102 log_debug("render_file_create: term_renderer created (%dx%d cells, %dx%d px)", cols, rows, actual_width_px,
103 actual_height_px);
104
105 err = ffmpeg_encoder_create(output_path, actual_width_px, actual_height_px, fps, &ctx->encoder);
106 if (err != ASCIICHAT_OK) {
107 log_error("render_file_create: ffmpeg_encoder_create failed: %s", asciichat_error_string(err));
109 SAFE_FREE(ctx);
110 return err;
111 }
112 log_debug("render_file_create: ffmpeg_encoder created successfully");
113
114 // Initialize audio fields
115 ctx->audio_sample_rate = 48000; // Audio pipeline is 48kHz
116 ctx->fps = fps;
117 // Allocate large buffer for snapshot mode (5 seconds at 48kHz with ~14 FPS = 3,428 samples per frame)
118 // Use 8192 to comfortably handle snapshot reads without excessive overhead
119 ctx->audio_buf_size = 8192; // Temporary buffer for reading audio (in floats)
120 ctx->audio_read_buf = SAFE_MALLOC(ctx->audio_buf_size * sizeof(float), float *);
121
123 if (err != ASCIICHAT_OK) {
127 SAFE_FREE(ctx);
128 return err;
129 }
130 log_info("renderer: initialized encoder for %s", output_path);
131
132 // Register render file context for debugging
133 NAMED_REGISTER_CONTEXT(ctx, "render_file_ctx", output_path, NULL);
134
135 *out = ctx;
136 return ASCIICHAT_OK;
137}
asciichat_error_t ffmpeg_encoder_destroy(ffmpeg_encoder_t *enc)
asciichat_error_t ffmpeg_encoder_create(const char *output_path, int width_px, int height_px, int fps, ffmpeg_encoder_t **out)
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define SAFE_CALLOC(count, size, cast)
Definition common.h:274
unsigned char uint8_t
Definition common.h:56
#define NAMED_REGISTER_CONTEXT(context, context_type, name, parent_ptr)
Register a generic context with automatic format specifier.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ASCIICHAT_OK
Definition error_codes.h:51
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
int term_renderer_width_px(terminal_renderer_t *r)
asciichat_error_t term_renderer_create(const term_renderer_config_t *cfg, terminal_renderer_t **out)
void term_renderer_destroy(terminal_renderer_t *r)
int term_renderer_height_px(terminal_renderer_t *r)
asciichat_error_t platform_font_resolve(const char *spec, char *out, size_t out_size, bool *out_is_path, const uint8_t **out_font_data, size_t *out_font_data_size)
bool terminal_has_dark_background(void)
Detect if terminal theme is dark.
asciichat_error_t audio_recording_create(audio_recording_t **out)
Definition recording.c:22
term_renderer_theme_t
Terminal rendering theme.
Definition renderer.h:26
@ TERM_RENDERER_THEME_LIGHT
Definition renderer.h:28
@ TERM_RENDERER_THEME_DARK
Definition renderer.h:27
@ TERM_RENDERER_THEME_AUTO
Definition renderer.h:29
terminal_renderer_t * renderer
Definition renderer.c:18
ffmpeg_encoder_t * encoder
Definition renderer.c:19
uint32_t audio_sample_rate
Definition renderer.c:27
audio_recording_t * recording
Definition renderer.c:20
float * audio_read_buf
Definition renderer.c:29

References ASCIICHAT_OK, render_file_ctx_s::audio_buf_size, render_file_ctx_s::audio_read_buf, audio_recording_create(), render_file_ctx_s::audio_sample_rate, term_renderer_config_t::cols, render_file_ctx_s::encoder, ffmpeg_encoder_create(), ffmpeg_encoder_destroy(), term_renderer_config_t::font_spec, render_file_ctx_s::fps, GET_OPTION, log_debug, log_error, log_info, log_warn, NAMED_REGISTER_CONTEXT, platform_font_resolve(), render_file_ctx_s::recording, render_file_ctx_s::renderer, SAFE_CALLOC, SAFE_FREE, SAFE_MALLOC, SAFE_STRNCPY, term_renderer_create(), term_renderer_destroy(), term_renderer_height_px(), TERM_RENDERER_THEME_AUTO, TERM_RENDERER_THEME_DARK, TERM_RENDERER_THEME_LIGHT, term_renderer_width_px(), and terminal_has_dark_background().

Referenced by session_display_init_render_file().

◆ render_file_destroy()

asciichat_error_t render_file_destroy ( render_file_ctx_t *  ctx)

Definition at line 247 of file renderer.c.

247 {
248 if (!ctx)
249 return ASCIICHAT_OK;
250 uint64_t target = ctx->audio_target;
252 while (ctx->audio_samples_written < target) {
253 int count = (int)((target - ctx->audio_samples_written) > (uint64_t)ctx->audio_buf_size
255 : target - ctx->audio_samples_written);
257 err = ffmpeg_encoder_write_audio(ctx->encoder, ctx->audio_read_buf, count);
258 if (err != ASCIICHAT_OK)
259 break;
260 ctx->audio_samples_written += count;
261 }
264 if (err == ASCIICHAT_OK)
265 err = close_err;
268 SAFE_FREE(ctx);
269 return err;
270}
asciichat_error_t ffmpeg_encoder_write_audio(ffmpeg_encoder_t *enc, const float *samples, int num_samples)
unsigned long long uint64_t
Definition common.h:59
void audio_recording_read(audio_recording_t *recording, float *samples, size_t count)
Definition recording.c:87
void audio_recording_destroy(audio_recording_t *recording)
Definition recording.c:39
uint64_t audio_samples_written
Definition renderer.c:22
uint64_t audio_target
Definition renderer.c:25

References ASCIICHAT_OK, render_file_ctx_s::audio_buf_size, render_file_ctx_s::audio_read_buf, audio_recording_destroy(), audio_recording_read(), render_file_ctx_s::audio_samples_written, render_file_ctx_s::audio_target, render_file_ctx_s::encoder, ffmpeg_encoder_destroy(), ffmpeg_encoder_write_audio(), render_file_ctx_s::recording, render_file_ctx_s::renderer, SAFE_FREE, and term_renderer_destroy().

Referenced by session_display_destroy().

◆ render_file_set_live_timing()

void render_file_set_live_timing ( render_file_ctx_t *  ctx)

Definition at line 139 of file renderer.c.

139 {
140 if (ctx) {
141 ctx->live_timing = true;
143 }
144}
void ffmpeg_encoder_set_live_timing(ffmpeg_encoder_t *enc)

References render_file_ctx_s::encoder, ffmpeg_encoder_set_live_timing(), and render_file_ctx_s::live_timing.

Referenced by session_display_render_frame(), and session_display_set_render_live_timing().

◆ render_file_set_snapshot_actual_duration()

void render_file_set_snapshot_actual_duration ( render_file_ctx_t *  ctx,
double  actual_duration_sec 
)

Definition at line 241 of file renderer.c.

241 {
242 if (!ctx || !ctx->encoder)
243 return;
244 ffmpeg_encoder_set_snapshot_actual_duration(ctx->encoder, actual_duration_sec);
245}
void ffmpeg_encoder_set_snapshot_actual_duration(ffmpeg_encoder_t *enc, double actual_duration_sec)

References render_file_ctx_s::encoder, and ffmpeg_encoder_set_snapshot_actual_duration().

Referenced by session_display_set_snapshot_actual_duration().

◆ render_file_write_frame()

asciichat_error_t render_file_write_frame ( render_file_ctx_t *  ctx,
const char *  ansi_frame,
uint64_t  captured_ns 
)

Definition at line 146 of file renderer.c.

146 {
147
148 log_info("render_file_write_frame: CALLED - ctx=%p, captured_ns=%llu", (void *)ctx, (unsigned long long)captured_ns);
149
150 if (!ctx) {
151 log_warn("render_file_write_frame: ctx is NULL");
152 return ASCIICHAT_OK;
153 }
154
155 if (!ansi_frame) {
156 log_warn("render_file_write_frame: ansi_frame is NULL");
157 return ASCIICHAT_OK;
158 }
159
160 size_t frame_len = strlen(ansi_frame);
161 uint64_t frame_index = ctx->frames_written;
162 if (ctx->live_timing && ctx->frames_written > 0) {
163 uint64_t elapsed = captured_ns > ctx->first_frame_ns ? captured_ns - ctx->first_frame_ns : 0;
164 frame_index = elapsed / 1000000000ULL * ctx->fps + elapsed % 1000000000ULL * ctx->fps / 1000000000ULL;
165 if (frame_index <= ctx->last_frame_index)
166 return ASCIICHAT_OK;
167 }
168 log_info("render_file_write_frame: processing frame (len=%zu)", frame_len);
169 if (frame_len > 0) {
170 log_info(" first 100 chars: %.100s", ansi_frame);
171 }
172
173 asciichat_error_t err = term_renderer_feed(ctx->renderer, ansi_frame, frame_len);
174 if (err != ASCIICHAT_OK) {
175 log_warn("render_file_write_frame: term_renderer_feed failed: %s", asciichat_error_string(err));
176 return err;
177 }
178
179 const uint8_t *pixels = term_renderer_pixels(ctx->renderer);
180 int pitch = term_renderer_pitch(ctx->renderer);
181 int width_px = term_renderer_width_px(ctx->renderer);
182 int height_px = term_renderer_height_px(ctx->renderer);
183
184 log_info("render_file_write_frame: pixels=%p pitch=%d dims=%dx%d", (void *)pixels, pitch, width_px, height_px);
185
186 if (pixels) {
187 uint8_t sample_r = pixels[0], sample_g = pixels[1], sample_b = pixels[2], sample_a = pixels[3];
188 size_t mid_offset = (size_t)(height_px / 2) * (size_t)pitch + (size_t)(width_px / 2) * 4;
189 uint8_t sample_r_mid = pixels[mid_offset], sample_g_mid = pixels[mid_offset + 1],
190 sample_b_mid = pixels[mid_offset + 2], sample_a_mid = pixels[mid_offset + 3];
191
192 log_info(" pixel[0,0]: RGBA(%u,%u,%u,%u), pixel[%d,%d]: RGBA(%u,%u,%u,%u)", sample_r, sample_g, sample_b, sample_a,
193 width_px / 2, height_px / 2, sample_r_mid, sample_g_mid, sample_b_mid, sample_a_mid);
194 }
195
196 // term_renderer_pixels() returns a pointer to the renderer's internal buffer which gets
197 // overwritten on the next frame. We must copy the data before passing to the encoder.
198 uint8_t *pixels_copy = NULL;
199 if (pixels) {
200 size_t pixel_buffer_size = (size_t)pitch * (size_t)height_px;
201 pixels_copy = SAFE_MALLOC(pixel_buffer_size, uint8_t *);
202 memcpy(pixels_copy, pixels, pixel_buffer_size);
203 log_info("render_file_write_frame: copied %zu bytes of pixel data (was at %p, now at %p)", pixel_buffer_size,
204 (void *)pixels, (void *)pixels_copy);
205 }
206
207 err = ffmpeg_encoder_write_frame(ctx->encoder, pixels_copy, pitch, captured_ns);
208 if (err != ASCIICHAT_OK) {
209 log_warn("render_file_write_frame: ffmpeg_encoder_write_frame failed: %s", asciichat_error_string(err));
210 }
211
212 // Free the pixel copy (ffmpeg_encoder_write_frame reads and converts it immediately)
213 SAFE_FREE(pixels_copy);
214
215 if (err == ASCIICHAT_OK) {
216 if (ctx->frames_written == 0) {
217 ctx->first_frame_ns = captured_ns;
218 audio_recording_start(ctx->recording, captured_ns);
219 }
220 ctx->frames_written++;
221 ctx->last_frame_index = frame_index;
222 uint64_t target = (ctx->live_timing ? frame_index + 1 : ctx->frames_written) * ctx->audio_sample_rate / ctx->fps;
223 ctx->audio_target = target;
224 /* Keep 100 ms pending so audio callbacks can deliver samples before encoding. */
225 uint64_t ready = target > 4800 ? target - 4800 : 0;
226 while (ctx->audio_samples_written < ready) {
227 int count = (int)((ready - ctx->audio_samples_written) > (uint64_t)ctx->audio_buf_size
228 ? (uint64_t)ctx->audio_buf_size
229 : ready - ctx->audio_samples_written);
230 audio_recording_read(ctx->recording, ctx->audio_read_buf, count);
231 err = ffmpeg_encoder_write_audio(ctx->encoder, ctx->audio_read_buf, count);
232 if (err != ASCIICHAT_OK)
233 break;
234 ctx->audio_samples_written += count;
235 }
236 }
237
238 return err;
239}
asciichat_error_t ffmpeg_encoder_write_frame(ffmpeg_encoder_t *enc, const uint8_t *rgb, int pitch, uint64_t captured_ns)
const uint8_t * term_renderer_pixels(terminal_renderer_t *r)
asciichat_error_t term_renderer_feed(terminal_renderer_t *r, const char *ansi_frame, size_t len)
int term_renderer_pitch(terminal_renderer_t *r)
void audio_recording_start(audio_recording_t *recording, uint64_t epoch_ns)
Definition recording.c:50

References ASCIICHAT_OK, render_file_ctx_s::audio_buf_size, render_file_ctx_s::audio_read_buf, audio_recording_read(), audio_recording_start(), render_file_ctx_s::audio_sample_rate, render_file_ctx_s::audio_samples_written, render_file_ctx_s::audio_target, render_file_ctx_s::encoder, ffmpeg_encoder_write_audio(), ffmpeg_encoder_write_frame(), render_file_ctx_s::first_frame_ns, render_file_ctx_s::fps, render_file_ctx_s::frames_written, render_file_ctx_s::last_frame_index, render_file_ctx_s::live_timing, log_info, log_warn, render_file_ctx_s::recording, render_file_ctx_s::renderer, SAFE_FREE, SAFE_MALLOC, term_renderer_feed(), term_renderer_height_px(), term_renderer_pitch(), term_renderer_pixels(), and term_renderer_width_px().

Referenced by session_display_encode_frame(), and session_display_render_frame().