ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
encoder.c
Go to the documentation of this file.
1#include <defer.h>
2#include <ascii-chat/otel.h>
8
10#include <ascii-chat/common.h>
12#include <ascii-chat/log/io.h>
14#include <libplatform/system.h>
15
16#include <libavcodec/avcodec.h>
17#include <libavutil/frame.h>
18#include <libavutil/imgutils.h>
19#include <libavutil/opt.h>
20
21#include <string.h>
22#include <stdlib.h>
23#include <unistd.h>
24
25static int h265_thread_count(void) {
26#ifdef _WIN32
27 SYSTEM_INFO sysinfo;
28 GetSystemInfo(&sysinfo);
29 long cores = (long)sysinfo.dwNumberOfProcessors;
30#else
31 long cores = sysconf(_SC_NPROCESSORS_ONLN);
32#endif
33 if (cores <= 0)
34 cores = 4;
35 int half = (int)(cores / 2);
36 return half > 4 ? half : 4;
37}
38
39static bool is_hardware_encoder(const AVCodec *codec) {
40 if (!codec || !codec->name)
41 return false;
42 return strstr(codec->name, "_d3d12va") != NULL || strstr(codec->name, "_nvenc") != NULL ||
43 strstr(codec->name, "_qsv") != NULL || strstr(codec->name, "_amf") != NULL ||
44 strstr(codec->name, "_mf") != NULL;
45}
46
47static const AVCodec *video_encoder_find_codec(video_codec_t codec_type) {
48 const AVCodec *codec = avcodec_find_encoder_by_name(codec_type == VIDEO_CODEC_H264 ? "libx264" : "libx265");
49 if (!codec)
50 codec = avcodec_find_encoder(codec_type == VIDEO_CODEC_H264 ? AV_CODEC_ID_H264 : AV_CODEC_ID_HEVC);
51 return is_hardware_encoder(codec) ? NULL : codec;
52}
53
55 if (codec != VIDEO_CODEC_H265 && codec != VIDEO_CODEC_H264)
56 return false;
57 return video_encoder_find_codec(codec) != NULL;
58}
59
85
86h265_encoder_t *h265_encoder_create(uint16_t initial_width, uint16_t initial_height) {
87 ASCII_OTEL_SCOPE(H265_ENCODER_CREATE);
88 return video_encoder_create(VIDEO_CODEC_H265, initial_width, initial_height);
89}
90
91h265_encoder_t *video_encoder_create(video_codec_t codec_type, uint16_t initial_width, uint16_t initial_height) {
92 ASCII_OTEL_SCOPE(VIDEO_ENCODER_CREATE);
93 ASCII_OTEL_ATTR_STRING("video.codec", codec_type == VIDEO_CODEC_H264 ? "h264" : "h265");
94 ASCII_OTEL_ATTR_INT("video.frame.width", initial_width);
95 ASCII_OTEL_ATTR_INT("video.frame.height", initial_height);
96 if (codec_type != VIDEO_CODEC_H265 && codec_type != VIDEO_CODEC_H264) {
97 SET_ERRNO(ERROR_INVALID_PARAM, "Expected HEVC or H.264 codec");
98 return NULL;
99 }
100 if (initial_width == 0 || initial_height == 0) {
101 SET_ERRNO(ERROR_MEDIA_INIT, "Frame dimensions must be non-zero");
102 return NULL;
103 }
104
106 if (!enc) {
107 SET_ERRNO(ERROR_MEMORY, "Failed to allocate encoder structure");
108 return NULL;
109 }
110
111 defer_named(cleanup, h265_encoder_destroy(enc));
112
113 const AVCodec *codec = video_encoder_find_codec(codec_type);
114 if (!codec) {
115 SET_ERRNO(ERROR_MEDIA_INIT, "%s encoder not found", codec_type == VIDEO_CODEC_H264 ? "H.264" : "HEVC");
116 return NULL;
117 }
118 enc->codec = codec;
119
120 enc->codec_ctx = avcodec_alloc_context3(codec);
121 if (!enc->codec_ctx) {
122 SET_ERRNO(ERROR_MEMORY, "Failed to allocate codec context");
123 return NULL;
124 }
125
126 // Configure codec context
127 enc->codec_ctx->width = initial_width;
128 enc->codec_ctx->height = initial_height;
129 enc->codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
130 enc->codec_ctx->time_base = (AVRational){1, 30};
131 enc->codec_ctx->framerate = (AVRational){30, 1};
132
133 // Set ultrafast preset for low-latency encoding
134 if (av_opt_set(enc->codec_ctx->priv_data, "preset", "ultrafast", 0) < 0) {
135 log_warn("Failed to set preset to ultrafast, using default");
136 }
137 if (av_opt_set(enc->codec_ctx->priv_data, "tune", "zerolatency", 0) < 0) {
138 log_warn("Failed to set tune to zerolatency, using default");
139 }
140
141 enc->codec_ctx->thread_count = h265_thread_count();
142
143 // Defer avcodec_open2 to first encode call. The initial resolution (800x600) is
144 // usually wrong — the actual frame resolution is only known when the first capture
145 // arrives. Opening the codec here would create an x265 thread pool (~1s) that gets
146 // immediately discarded on reconfigure, wasting startup time.
147 enc->codec_opened = false;
148
149 // Allocate frame and packet
150 enc->frame = av_frame_alloc();
151 enc->packet = av_packet_alloc();
152
153 if (!enc->frame || !enc->packet) {
154 SET_ERRNO(ERROR_MEMORY, "Failed to allocate frame/packet");
155 return NULL;
156 }
157
158 enc->current_width = initial_width;
159 enc->current_height = initial_height;
160
161 // Allocate YUV420P buffer (Y plane + U/V planes)
162 size_t stride = initial_width;
163 enc->yuv_buf_size = stride * initial_height * 3 / 2;
164 enc->yuv_buf = SAFE_MALLOC(enc->yuv_buf_size, uint8_t *);
165 if (!enc->yuv_buf) {
166 SET_ERRNO(ERROR_MEMORY, "Failed to allocate YUV buffer");
167 return NULL;
168 }
169
170 log_info("FFmpeg video encoder created: %ux%u (codec: %s)", initial_width, initial_height, codec->name);
171
172 // Register encoder for debugging
173 NAMED_REGISTER_H265_ENCODER(enc, "h265_encoder", NULL);
174
175 defer_cancel(cleanup);
176 return enc;
177}
178
180 if (!encoder)
181 return;
182
183 if (encoder->frame) {
184 av_frame_free(&encoder->frame);
185 }
186 if (encoder->packet) {
187 av_packet_free(&encoder->packet);
188 }
189 if (encoder->codec_ctx) {
190 LOG_IO("video encoder", { avcodec_free_context(&encoder->codec_ctx); });
191 }
192 if (encoder->yuv_buf) {
193 SAFE_FREE(encoder->yuv_buf);
194 }
195 SAFE_FREE(encoder);
196}
197
198static asciichat_error_t h265_encoder_open_codec(h265_encoder_t *encoder, uint16_t width, uint16_t height) {
199 // Free existing context if re-opening at a different resolution
200 if (encoder->codec_ctx) {
201 LOG_IO("video encoder", { avcodec_free_context(&encoder->codec_ctx); });
202 }
203
204 const AVCodec *codec = encoder->codec;
205 encoder->codec_ctx = avcodec_alloc_context3(codec);
206 if (!encoder->codec_ctx) {
207 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate codec context for %ux%u", width, height);
208 }
209
210 encoder->codec_ctx->width = width;
211 encoder->codec_ctx->height = height;
212 encoder->codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
213 encoder->codec_ctx->time_base = (AVRational){1, 30};
214 encoder->codec_ctx->framerate = (AVRational){30, 1};
215 encoder->codec_ctx->thread_count = h265_thread_count();
216 encoder->codec_ctx->max_b_frames = 0;
217 encoder->codec_ctx->gop_size = 30;
218 // Recovery frames must be independently decodable after discarded output.
219 if (encoder->codec->id == AV_CODEC_ID_H264) {
220 av_opt_set(encoder->codec_ctx->priv_data, "x264-params", "repeat-headers=1", 0);
221 av_opt_set(encoder->codec_ctx->priv_data, "forced-idr", "1", 0);
222 } else {
223 // FFmpeg can override zerolatency's frame-thread count with thread_count.
224 // Keep parallelism within a frame so each submitted frame is available immediately.
225 av_opt_set(encoder->codec_ctx->priv_data, "x265-params", "repeat-headers=1:open-gop=0:frame-threads=1", 0);
226 av_opt_set(encoder->codec_ctx->priv_data, "forced-idr", "1", 0);
227 }
228
229 if (av_opt_set(encoder->codec_ctx->priv_data, "preset", "ultrafast", 0) < 0) {
230 log_warn("Failed to set preset to ultrafast");
231 }
232 if (av_opt_set(encoder->codec_ctx->priv_data, "tune", "zerolatency", 0) < 0) {
233 log_warn("Failed to set tune to zerolatency");
234 }
235
236 log_dev("[H265_OPEN] avcodec_open2 for %ux%u (threads=%d)", width, height, encoder->codec_ctx->thread_count);
237 int codec_open_result = 0;
238 LOG_IO("video encoder", { codec_open_result = avcodec_open2(encoder->codec_ctx, codec, NULL); });
239
240 if (codec_open_result < 0) {
241 return SET_ERRNO(ERROR_MEDIA_INIT, "Failed to open %s encoder for %ux%u", codec->name, width, height);
242 }
243
244 // Resize YUV buffer if needed
245 size_t new_yuv_size = width * height * 3 / 2;
246 if (new_yuv_size > encoder->yuv_buf_size) {
247 uint8_t *new_buf = SAFE_MALLOC(new_yuv_size, uint8_t *);
248 if (!new_buf) {
249 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate YUV buffer for %ux%u", width, height);
250 }
251 SAFE_FREE(encoder->yuv_buf);
252 encoder->yuv_buf = new_buf;
253 encoder->yuv_buf_size = new_yuv_size;
254 }
255
256 encoder->current_width = width;
257 encoder->current_height = height;
258 encoder->codec_opened = true;
259 encoder->request_keyframe = true;
260
261 log_info("Video encoder %s opened for %ux%u", codec->name, width, height);
262 return ASCIICHAT_OK;
263}
264
265static asciichat_error_t h265_encoder_reconfigure(h265_encoder_t *encoder, uint16_t new_width, uint16_t new_height) {
266 ASCII_OTEL_SCOPE(H265_ENCODER_RECONFIGURE);
267 if (encoder->codec_opened && encoder->current_width == new_width && encoder->current_height == new_height) {
268 return ASCIICHAT_OK;
269 }
270 return h265_encoder_open_codec(encoder, new_width, new_height);
271}
272
273static void h265_encoder_ascii_to_yuv420(const uint8_t *rgb_data, uint16_t width, uint16_t height, uint8_t *yuv_buf) {
274 uint8_t *y_plane = yuv_buf;
275 uint8_t *u_plane = y_plane + width * height;
276 uint8_t *v_plane = u_plane + (width / 2) * (height / 2);
277
278 // Convert RGB24 to YUV420p
279 // Y = 0.299*R + 0.587*G + 0.114*B (standard luma formula)
280 // U = -0.14713*R - 0.28886*G + 0.436*B (offset by 128)
281 // V = 0.615*R - 0.51499*G - 0.10001*B (offset by 128)
282
283 // Process Y plane (each pixel)
284 for (uint32_t i = 0; i < (uint32_t)width * (uint32_t)height; i++) {
285 uint8_t r = rgb_data[i * 3];
286 uint8_t g = rgb_data[i * 3 + 1];
287 uint8_t b = rgb_data[i * 3 + 2];
288
289 // Calculate Y (luma)
290 y_plane[i] = (uint8_t)(((66 * r + 129 * g + 25 * b + 128) >> 8) + 16);
291 }
292
293 // Process U and V planes (subsampled 2x2)
294 for (uint32_t y = 0; y < (uint32_t)height; y += 2) {
295 for (uint32_t x = 0; x < (uint32_t)width; x += 2) {
296 // Average 2x2 RGB block for chroma
297 uint32_t idx00 = ((y)*width + x) * 3;
298 uint32_t idx10 = ((y)*width + x + 1) * 3;
299 uint32_t idx01 = ((y + 1) * width + x) * 3;
300 uint32_t idx11 = ((y + 1) * width + x + 1) * 3;
301
302 uint16_t r_avg = (rgb_data[idx00] + rgb_data[idx10] + rgb_data[idx01] + rgb_data[idx11]) / 4;
303 uint16_t g_avg = (rgb_data[idx00 + 1] + rgb_data[idx10 + 1] + rgb_data[idx01 + 1] + rgb_data[idx11 + 1]) / 4;
304 uint16_t b_avg = (rgb_data[idx00 + 2] + rgb_data[idx10 + 2] + rgb_data[idx01 + 2] + rgb_data[idx11 + 2]) / 4;
305
306 // Calculate U (Cb)
307 int16_t u = (int16_t)((((-38 * r_avg - 74 * g_avg + 112 * b_avg + 128) >> 8) + 128));
308 u_plane[(y / 2) * (width / 2) + (x / 2)] = (uint8_t)(u < 0 ? 0 : (u > 255 ? 255 : u));
309
310 // Calculate V (Cr)
311 int16_t v = (int16_t)((((112 * r_avg - 94 * g_avg - 18 * b_avg + 128) >> 8) + 128));
312 v_plane[(y / 2) * (width / 2) + (x / 2)] = (uint8_t)(v < 0 ? 0 : (v > 255 ? 255 : v));
313 }
314 }
315}
316
318 uint8_t *output_buf, size_t *output_size) {
319 ASCII_OTEL_SCOPE(H265_ENCODE);
320 ASCII_OTEL_ATTR_INT("video.frame.width", width);
321 ASCII_OTEL_ATTR_INT("video.frame.height", height);
322 if (encoder && encoder->codec)
323 ASCII_OTEL_ATTR_STRING("video.codec", encoder->codec->id == AV_CODEC_ID_H264 ? "h264" : "h265");
324 if (!encoder || !ascii_data || !output_buf || !output_size) {
325 return SET_ERRNO(ERROR_INTERNAL, "Invalid encoder arguments");
326 }
327
328 if (!width || !height || (width & 1) || (height & 1)) {
329 return SET_ERRNO(ERROR_MEDIA_INIT, "Video encoding requires non-zero even dimensions, got %ux%u", width, height);
330 }
331
332 if (*output_size < 5) {
333 return SET_ERRNO(ERROR_NETWORK_SIZE, "Output buffer too small (minimum 5 bytes)");
334 }
335
336 bool size_changed = width != encoder->current_width || height != encoder->current_height;
337 asciichat_error_t result = h265_encoder_reconfigure(encoder, width, height);
338 if (result != ASCIICHAT_OK) {
339 return result;
340 }
341
342 h265_encoder_ascii_to_yuv420(ascii_data, width, height, encoder->yuv_buf);
343
344 // Setup frame data pointers
345 encoder->frame->data[0] = encoder->yuv_buf;
346 encoder->frame->data[1] = encoder->yuv_buf + width * height;
347 encoder->frame->data[2] = encoder->yuv_buf + width * height + (width / 2) * (height / 2);
348 encoder->frame->linesize[0] = width;
349 encoder->frame->linesize[1] = width / 2;
350 encoder->frame->linesize[2] = width / 2;
351 encoder->frame->width = width;
352 encoder->frame->height = height;
353 encoder->frame->format = AV_PIX_FMT_YUV420P;
354
355 encoder->frame->pts = (int64_t)encoder->frames_submitted++;
356 encoder->frame->pict_type = AV_PICTURE_TYPE_NONE;
357
358 // Request keyframe if needed
359 if (encoder->request_keyframe) {
360 encoder->frame->pict_type = AV_PICTURE_TYPE_I;
361 encoder->request_keyframe = false;
362 }
363
364 // Encode frame
365 uint64_t stats_codec_start = time_get_ns();
366 int send_ret = avcodec_send_frame(encoder->codec_ctx, encoder->frame);
367 if (send_ret < 0) {
368 stats_duration_record(stats_runtime_scope(), STATS_DURATION_ENCODE, time_get_ns() - stats_codec_start);
369 log_error("avcodec_send_frame failed: %d", send_ret);
370 return SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to send frame to encoder");
371 }
372
373 int recv_ret = avcodec_receive_packet(encoder->codec_ctx, encoder->packet);
374 stats_duration_record(stats_runtime_scope(), STATS_DURATION_ENCODE, time_get_ns() - stats_codec_start);
375 if (recv_ret == AVERROR(EAGAIN)) {
376 // EAGAIN is normal - encoder needs more frames before outputting a packet
377 log_dev("EAGAIN - encoder buffering frames (normal), waiting for next frame");
378 *output_size = 0; // No output this time
379 return ASCIICHAT_OK;
380 }
381 if (recv_ret < 0) {
382 log_error("avcodec_receive_packet failed: %d (EAGAIN=%d)", recv_ret, AVERROR(EAGAIN));
383 return SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to receive encoded packet");
384 }
385 log_debug_every(US_PER_SEC_INT, "Got encoded packet: size=%d", (int)encoder->packet->size);
386
387 uint8_t flags = 0;
388 if (encoder->packet->flags & AV_PKT_FLAG_KEY) {
390 encoder->keyframes++;
391 }
392
393 if (size_changed) {
395 }
396
397 size_t header_size = 5;
398 size_t packet_size = encoder->packet->size;
399 size_t required_size = header_size + packet_size;
400
401 if (required_size > *output_size) {
402 return SET_ERRNO(ERROR_NETWORK_SIZE, "Output buffer too small: need %zu, have %zu", required_size, *output_size);
403 }
404
405 // Write header: [flags][width:u16][height:u16]
406 output_buf[0] = flags;
407 output_buf[1] = (width >> 8) & 0xFF;
408 output_buf[2] = width & 0xFF;
409 output_buf[3] = (height >> 8) & 0xFF;
410 output_buf[4] = height & 0xFF;
411
412 // Write encoded data
413 if (packet_size > 0) {
414 memcpy(output_buf + header_size, encoder->packet->data, packet_size);
415 }
416
417 *output_size = required_size;
418 ascii_otel_metric(ASCII_OTEL_METRIC_VIDEO_BYTES, (double)required_size);
419 if (encoder->packet->flags & AV_PKT_FLAG_KEY)
420 ascii_otel_metric(ASCII_OTEL_METRIC_VIDEO_KEYFRAMES, 1);
421 ASCII_OTEL_ATTR_INT("video.packet.size", (int64_t)required_size);
422 ASCII_OTEL_ATTR_INT("video.frame.keyframe", (encoder->packet->flags & AV_PKT_FLAG_KEY) != 0);
423 ascii_otel_metric(ASCII_OTEL_METRIC_VIDEO_ENCODE_FRAMES, 1);
424 ascii_otel_metric(ASCII_OTEL_METRIC_VIDEO_ENCODE_INPUT, (double)width * height * 3);
425 ascii_otel_metric(ASCII_OTEL_METRIC_VIDEO_ENCODE_BYTES_IN, (double)width * height * 3);
426 ascii_otel_metric(ASCII_OTEL_METRIC_VIDEO_ENCODE_BYTES_OUT, (double)required_size);
427 encoder->total_frames++;
429 ascii_otel_metric(ASCII_OTEL_METRIC_VIDEO_WIDTH, encoder->frame->width);
430 ascii_otel_metric(ASCII_OTEL_METRIC_VIDEO_HEIGHT, encoder->frame->height);
431 if (required_size)
432 ascii_otel_metric(ASCII_OTEL_METRIC_VIDEO_RATIO,
433 (double)encoder->frame->width * encoder->frame->height * 3 / (double)required_size);
434 log_dev("[H265_ENCODE_DONE] Encoding complete, output_size=%zu", required_size);
435
436 return ASCIICHAT_OK;
437}
438
440 if (encoder) {
441 encoder->request_keyframe = true;
442 }
443}
444
445asciichat_error_t h265_encoder_flush(h265_encoder_t *encoder, uint8_t *output_buf, size_t *output_size) {
446 ASCII_OTEL_SCOPE(H265_ENCODER_FLUSH);
447 if (!encoder || !output_buf || !output_size) {
448 return SET_ERRNO(ERROR_INTERNAL, "Invalid encoder or output parameters for flush");
449 }
450
451 if (*output_size < 5) {
452 return SET_ERRNO(ERROR_NETWORK_SIZE, "Output buffer too small (minimum 5 bytes)");
453 }
454
455 if (!encoder->codec_opened) {
456 *output_size = 0;
457 return ASCIICHAT_OK;
458 }
459
460 // Send NULL frame to signal end of stream (flushes encoder)
461 log_debug("H265_FLUSH: Sending NULL frame to encoder to flush buffered data");
462 uint64_t stats_codec_start = time_get_ns();
463 int send_ret = avcodec_send_frame(encoder->codec_ctx, NULL);
464 if (send_ret < 0) {
465 stats_duration_record(stats_runtime_scope(), STATS_DURATION_ENCODE, time_get_ns() - stats_codec_start);
466 log_error("H265_FLUSH: avcodec_send_frame(NULL) failed: %d", send_ret);
467 return SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to flush encoder");
468 }
469
470 // Try to receive a packet
471 log_debug("H265_FLUSH: Attempting to receive flushed packet...");
472 int recv_ret = avcodec_receive_packet(encoder->codec_ctx, encoder->packet);
473 stats_duration_record(stats_runtime_scope(), STATS_DURATION_ENCODE, time_get_ns() - stats_codec_start);
474 if (recv_ret == AVERROR(EAGAIN)) {
475 log_dev("H265_FLUSH: EAGAIN - no more frames available");
476 *output_size = 0;
477 return ASCIICHAT_OK;
478 }
479 if (recv_ret == AVERROR_EOF) {
480 log_debug("H265_FLUSH: EOF - encoder is flushed");
481 *output_size = 0;
482 return ASCIICHAT_OK;
483 }
484 if (recv_ret < 0) {
485 log_error("H265_FLUSH: avcodec_receive_packet failed: %d", recv_ret);
486 return SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to receive flushed packet");
487 }
488
489 log_debug("H265_FLUSH: Got flushed packet: size=%d", (int)encoder->packet->size);
490
491 // Check if packet size exceeds output buffer
492 size_t header_size = 5;
493 size_t packet_size = encoder->packet->size;
494 size_t required_size = header_size + packet_size;
495
496 if (required_size > *output_size) {
497 return SET_ERRNO(ERROR_NETWORK_SIZE, "Output buffer too small: need %zu, have %zu", required_size, *output_size);
498 }
499
500 // Write header: [flags][width:u16][height:u16]
501 uint8_t flags = 0;
502 if (encoder->packet->flags & AV_PKT_FLAG_KEY) {
504 encoder->keyframes++;
505 }
506
507 output_buf[0] = flags;
508 output_buf[1] = (encoder->current_width >> 8) & 0xFF;
509 output_buf[2] = encoder->current_width & 0xFF;
510 output_buf[3] = (encoder->current_height >> 8) & 0xFF;
511 output_buf[4] = encoder->current_height & 0xFF;
512
513 // Write encoded data
514 if (packet_size > 0) {
515 memcpy(output_buf + header_size, encoder->packet->data, packet_size);
516 }
517
518 *output_size = required_size;
519 ASCII_OTEL_ATTR_INT("video.packet.size", (int64_t)required_size);
520 if (encoder->codec)
521 ASCII_OTEL_ATTR_STRING("video.codec", encoder->codec->id == AV_CODEC_ID_H264 ? "h264" : "h265");
522 ascii_otel_metric(ASCII_OTEL_METRIC_VIDEO_ENCODE_FRAMES, 1);
523 ascii_otel_metric(ASCII_OTEL_METRIC_VIDEO_ENCODE_BYTES_OUT, (double)required_size);
524 ascii_otel_metric(ASCII_OTEL_METRIC_VIDEO_BYTES, (double)required_size);
525 if (encoder->packet->flags & AV_PKT_FLAG_KEY)
526 ascii_otel_metric(ASCII_OTEL_METRIC_VIDEO_KEYFRAMES, 1);
527 encoder->total_frames++;
529
530 return ASCIICHAT_OK;
531}
532
533void h265_encoder_get_stats(h265_encoder_t *encoder, uint64_t *total_frames, uint64_t *keyframes,
534 uint32_t *avg_bitrate) {
535 if (!encoder)
536 return;
537
538 if (total_frames)
539 *total_frames = encoder->total_frames;
540 if (keyframes)
541 *keyframes = encoder->keyframes;
542 if (avg_bitrate)
543 *avg_bitrate = encoder->avg_bitrate;
544}
#define SET_ERRNO(code,...)
video_codec_t
Definition codecs.h:28
@ VIDEO_CODEC_H264
H.264 (AVC) video codec.
Definition codecs.h:32
@ VIDEO_CODEC_H265
H.265 (HEVC) video codec.
Definition codecs.h:30
⚙️ Common definitions, error codes, macros, and types shared throughout the application
Named object registry for debugging — log identifiable resource names.
asciichat_error_t h265_encode(h265_encoder_t *encoder, uint16_t width, uint16_t height, const uint8_t *ascii_data, uint8_t *output_buf, size_t *output_size)
Definition encoder.c:317
void h265_encoder_get_stats(h265_encoder_t *encoder, uint64_t *total_frames, uint64_t *keyframes, uint32_t *avg_bitrate)
Definition encoder.c:533
bool video_encoder_is_available(video_codec_t codec)
Definition encoder.c:54
h265_encoder_t * h265_encoder_create(uint16_t initial_width, uint16_t initial_height)
Definition encoder.c:86
asciichat_error_t h265_encoder_flush(h265_encoder_t *encoder, uint8_t *output_buf, size_t *output_size)
Definition encoder.c:445
void h265_encoder_destroy(h265_encoder_t *encoder)
Definition encoder.c:179
void h265_encoder_request_keyframe(h265_encoder_t *encoder)
Definition encoder.c:439
struct h265_encoder h265_encoder_t
h265 encoder is intentionally a small, concrete record. Fields such as codec_ctx, codec,...
h265_encoder_t * video_encoder_create(video_codec_t codec_type, uint16_t initial_width, uint16_t initial_height)
Definition encoder.c:91
HEVC/H.265 encoder for ASCII art frames using libx265.
#define H265_ENCODER_FLAG_KEYFRAME
Definition encoder.h:34
#define H265_ENCODER_FLAG_SIZE_CHANGE
Definition encoder.h:35
unsigned int height
Definition fft.c:23
unsigned int width
Definition fft.c:23
unsigned short uint16_t
Definition common.h:61
unsigned int uint32_t
Definition common.h:62
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_MALLOC(size, cast)
Definition common.h:265
#define SAFE_CALLOC(count, size, cast)
Definition common.h:275
unsigned long long uint64_t
Definition common.h:63
unsigned char uint8_t
Definition common.h:60
#define NAMED_REGISTER_H265_ENCODER(encoder, name, parent_ptr)
Register an H.265 encoder with automatic format specifier.
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_MEDIA_DECODE
Definition error_codes.h:67
@ ERROR_NETWORK_SIZE
Definition error_codes.h:77
@ ERROR_MEDIA_INIT
Definition error_codes.h:65
@ ERROR_INVALID_PARAM
@ ERROR_INTERNAL
Definition error_codes.h:87
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
#define log_dev(...)
Log a DEV message (most verbose, development only).
Definition log/log.h:581
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
#define log_info(...)
Log an INFO message.
Definition log/log.h:608
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
#define US_PER_SEC_INT
Definition time.h:173
#define EAGAIN
Capture stdout/stderr and redirect to logging system.
#define LOG_IO(prefix, block)
Definition io.h:104
void stats_counter_add(stats_scope_t *scope, stats_counter_id_t id, uint64_t amount)
void stats_duration_record(stats_scope_t *scope, stats_duration_id_t id, uint64_t elapsed_ns)
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:749
void ascii_otel_metric(ascii_otel_metric_t metric, double value)
Definition otel.c:161
#define ASCII_OTEL_SCOPE(id)
Definition otel.h:72
#define ASCII_OTEL_ATTR_INT(key, value)
Definition otel.h:67
#define ASCII_OTEL_ATTR_STRING(key, value)
Definition otel.h:66
stats_scope_t * stats_runtime_scope(void)
Definition runtime.c:80
🔤 String Manipulation and Shell Escaping Utilities
h265 encoder is intentionally a small, concrete record. Fields such as codec_ctx, codec,...
Definition encoder.c:65
uint64_t keyframes
Definition encoder.c:82
const AVCodec * codec
Definition encoder.c:67
uint64_t total_frames
Definition encoder.c:81
uint16_t current_height
Definition encoder.c:72
bool codec_opened
Definition encoder.c:77
AVFrame * frame
Definition encoder.c:68
uint16_t current_width
Definition encoder.c:71
uint64_t frames_submitted
Definition encoder.c:80
uint8_t * yuv_buf
Definition encoder.c:74
AVCodecContext * codec_ctx
Definition encoder.c:66
bool request_keyframe
Definition encoder.c:78
AVPacket * packet
Definition encoder.c:69
size_t yuv_buf_size
Definition encoder.c:75
uint32_t avg_bitrate
Definition encoder.c:83
⏱️ High-precision timing utilities using sokol_time.h and uthash