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

FFmpeg HEVC encoder for ASCII art frames. More...

Go to the source code of this file.

Data Structures

struct  h265_encoder
 

Typedefs

typedef struct h265_encoder h265_encoder_t
 

Functions

h265_encoder_t * h265_encoder_create (uint16_t initial_width, uint16_t initial_height)
 
void h265_encoder_destroy (h265_encoder_t *encoder)
 
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)
 
void h265_encoder_request_keyframe (h265_encoder_t *encoder)
 
asciichat_error_t h265_encoder_flush (h265_encoder_t *encoder, uint8_t *output_buf, size_t *output_size)
 
void h265_encoder_get_stats (h265_encoder_t *encoder, uint64_t *total_frames, uint64_t *keyframes, uint32_t *avg_bitrate)
 

Detailed Description

FFmpeg HEVC encoder for ASCII art frames.

Definition in file encoder.c.

Typedef Documentation

◆ h265_encoder_t

typedef struct h265_encoder h265_encoder_t

Function Documentation

◆ h265_encode()

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 
)

Encode an ASCII art frame

The encoder automatically detects size changes and reinitializes when needed. The output packet includes frame dimensions so decoder can reconfigure.

Parameters
encoderEncoder handle
widthCurrent frame width (characters)
heightCurrent frame height (characters)
ascii_dataRaw ASCII character grid (width × height bytes)
output_bufBuffer for encoded output
output_sizeInput: buffer size, Output: actual encoded size
Returns
Error code (ASCIICHAT_OK on success)

OUTPUT PACKET FORMAT: [flags: u8][width: u16][height: u16][x265_data...]

Definition at line 288 of file encoder.c.

289 {
290 log_info("[H265_ENCODE_1] Starting h265_encode %ux%u", width, height);
291 if (!encoder || !ascii_data || !output_buf || !output_size) {
292 return SET_ERRNO(ERROR_INTERNAL, "Invalid encoder arguments");
293 }
294
295 if (*output_size < 5) {
296 return SET_ERRNO(ERROR_NETWORK_SIZE, "Output buffer too small (minimum 5 bytes)");
297 }
298
299 log_info("[H265_ENCODE_2] Reconfigure %ux%u -> %ux%u", encoder->current_width, encoder->current_height, width,
300 height);
301 asciichat_error_t result = h265_encoder_reconfigure(encoder, width, height);
302 log_info("[H265_ENCODE_3] Reconfigure returned %d", result);
303 if (result != ASCIICHAT_OK) {
304 return result;
305 }
306
307 log_info("[H265_ENCODE_4] Converting ASCII to YUV420");
308 h265_encoder_ascii_to_yuv420(ascii_data, width, height, encoder->yuv_buf);
309 log_info("[H265_ENCODE_5] YUV420 conversion done");
310
311 // Setup frame data pointers
312 encoder->frame->data[0] = encoder->yuv_buf;
313 encoder->frame->data[1] = encoder->yuv_buf + width * height;
314 encoder->frame->data[2] = encoder->yuv_buf + width * height + (width / 2) * (height / 2);
315 encoder->frame->linesize[0] = width;
316 encoder->frame->linesize[1] = width / 2;
317 encoder->frame->linesize[2] = width / 2;
318 encoder->frame->width = width;
319 encoder->frame->height = height;
320 encoder->frame->format = AV_PIX_FMT_YUV420P;
321
322 // Request keyframe if needed
323 if (encoder->request_keyframe) {
324 encoder->frame->pict_type = AV_PICTURE_TYPE_I;
325 encoder->request_keyframe = false;
326 }
327
328 // Encode frame
329 log_info("[H265_ENCODE_6] Sending frame to encoder: %ux%u format=%d", width, height, encoder->frame->format);
330 int send_ret = avcodec_send_frame(encoder->codec_ctx, encoder->frame);
331 log_info("[H265_ENCODE_7] avcodec_send_frame returned %d", send_ret);
332 if (send_ret < 0) {
333 log_error("H265_ENCODE: avcodec_send_frame failed: %d", send_ret);
334 return SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to send frame to encoder");
335 }
336
337 log_info("[H265_ENCODE_8] Receiving encoded packet...");
338 int recv_ret = avcodec_receive_packet(encoder->codec_ctx, encoder->packet);
339 if (recv_ret == AVERROR(EAGAIN)) {
340 // EAGAIN is normal - encoder needs more frames before outputting a packet
341 log_dev("H265_ENCODE: EAGAIN - encoder buffering frames (normal), waiting for next frame");
342 *output_size = 0; // No output this time
343 return ASCIICHAT_OK;
344 }
345 if (recv_ret < 0) {
346 log_error("H265_ENCODE: avcodec_receive_packet failed: %d (EAGAIN=%d)", recv_ret, AVERROR(EAGAIN));
347 return SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to receive encoded packet");
348 }
349 log_debug("H265_ENCODE: Got packet: size=%d", (int)encoder->packet->size);
350
351 uint8_t flags = 0;
352 if (encoder->frame->pict_type == AV_PICTURE_TYPE_I) {
354 encoder->keyframes++;
355 }
356
357 if (width != encoder->current_width || height != encoder->current_height) {
359 }
360
361 size_t header_size = 5;
362 size_t packet_size = encoder->packet->size;
363 size_t required_size = header_size + packet_size;
364
365 if (required_size > *output_size) {
366 return SET_ERRNO(ERROR_NETWORK_SIZE, "Output buffer too small: need %zu, have %zu", required_size, *output_size);
367 }
368
369 // Write header: [flags][width:u16][height:u16]
370 output_buf[0] = flags;
371 output_buf[1] = (width >> 8) & 0xFF;
372 output_buf[2] = width & 0xFF;
373 output_buf[3] = (height >> 8) & 0xFF;
374 output_buf[4] = height & 0xFF;
375
376 // Write encoded data
377 if (packet_size > 0) {
378 memcpy(output_buf + header_size, encoder->packet->data, packet_size);
379 }
380
381 *output_size = required_size;
382 encoder->total_frames++;
383 log_dev("[H265_ENCODE_DONE] Encoding complete, output_size=%zu", required_size);
384
385 return ASCIICHAT_OK;
386}
#define H265_ENCODER_FLAG_KEYFRAME
Definition encoder.h:33
#define H265_ENCODER_FLAG_SIZE_CHANGE
Definition encoder.h:34
unsigned char uint8_t
Definition common.h:56
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_MEDIA_DECODE
Definition error_codes.h:72
@ ERROR_NETWORK_SIZE
Definition error_codes.h:82
@ ERROR_INTERNAL
Definition error_codes.h:92
#define log_dev(...)
Log a DEV message (most verbose, development only)
Definition log/log.h:534
#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 EAGAIN

References ASCIICHAT_OK, EAGAIN, ERROR_INTERNAL, ERROR_MEDIA_DECODE, ERROR_NETWORK_SIZE, H265_ENCODER_FLAG_KEYFRAME, H265_ENCODER_FLAG_SIZE_CHANGE, log_debug, log_dev, log_error, log_info, and SET_ERRNO.

◆ h265_encoder_create()

h265_encoder_t * h265_encoder_create ( uint16_t  initial_width,
uint16_t  initial_height 
)

Create a new x265 encoder for ASCII frames

Parameters
initial_widthInitial frame width (characters)
initial_heightInitial frame height (characters)
Returns
Encoder handle, or NULL on error

Definition at line 55 of file encoder.c.

55 {
56 if (initial_width == 0 || initial_height == 0) {
57 SET_ERRNO(ERROR_MEDIA_INIT, "Frame dimensions must be non-zero");
58 return NULL;
59 }
60
62 if (!enc) {
63 SET_ERRNO(ERROR_MEMORY, "Failed to allocate encoder structure");
64 return NULL;
65 }
66
67 // Find best available HEVC encoder (try by ID first, then by name for specific implementations)
68 const AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_HEVC);
69
70 // If generic HEVC encoder not found, try specific implementations by name
71 if (!codec) {
72 codec = avcodec_find_encoder_by_name("libx265");
73 }
74 if (!codec) {
75 codec = avcodec_find_encoder_by_name("hevc_vaapi");
76 }
77 if (!codec) {
78 codec = avcodec_find_encoder_by_name("hevc_videotoolbox");
79 }
80 if (!codec) {
81 codec = avcodec_find_encoder_by_name("hevc_nvenc");
82 }
83 if (!codec) {
84 codec = avcodec_find_encoder_by_name("hevc_qsv");
85 }
86
87 if (!codec) {
88 SET_ERRNO(ERROR_MEDIA_INIT, "HEVC encoder not found");
89 SAFE_FREE(enc);
90 return NULL;
91 }
92
93 enc->codec_ctx = avcodec_alloc_context3(codec);
94 if (!enc->codec_ctx) {
95 SET_ERRNO(ERROR_MEMORY, "Failed to allocate codec context");
96 SAFE_FREE(enc);
97 return NULL;
98 }
99
100 // Configure codec context
101 enc->codec_ctx->width = initial_width;
102 enc->codec_ctx->height = initial_height;
103 enc->codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
104 enc->codec_ctx->time_base = (AVRational){1, 30};
105 enc->codec_ctx->framerate = (AVRational){30, 1};
106
107 // Set ultrafast preset for low-latency encoding
108 if (av_opt_set(enc->codec_ctx->priv_data, "preset", "ultrafast", 0) < 0) {
109 log_warn("Failed to set preset to ultrafast, using default");
110 }
111 if (av_opt_set(enc->codec_ctx->priv_data, "tune", "zerolatency", 0) < 0) {
112 log_warn("Failed to set tune to zerolatency, using default");
113 }
114
115 enc->codec_ctx->thread_count = h265_thread_count();
116
117 // Defer avcodec_open2 to first encode call. The initial resolution (800x600) is
118 // usually wrong — the actual frame resolution is only known when the first capture
119 // arrives. Opening the codec here would create an x265 thread pool (~1s) that gets
120 // immediately discarded on reconfigure, wasting startup time.
121 enc->codec_opened = false;
122
123 // Allocate frame and packet
124 enc->frame = av_frame_alloc();
125 enc->packet = av_packet_alloc();
126
127 if (!enc->frame || !enc->packet) {
128 SET_ERRNO(ERROR_MEMORY, "Failed to allocate frame/packet");
129 if (enc->frame)
130 av_frame_free(&enc->frame);
131 if (enc->packet)
132 av_packet_free(&enc->packet);
133 avcodec_free_context(&enc->codec_ctx);
134 SAFE_FREE(enc);
135 return NULL;
136 }
137
138 enc->current_width = initial_width;
139 enc->current_height = initial_height;
140
141 // Allocate YUV420P buffer (Y plane + U/V planes)
142 size_t stride = initial_width;
143 enc->yuv_buf_size = stride * initial_height * 3 / 2;
144 enc->yuv_buf = SAFE_MALLOC(enc->yuv_buf_size, uint8_t *);
145 if (!enc->yuv_buf) {
146 SET_ERRNO(ERROR_MEMORY, "Failed to allocate YUV buffer");
147 av_frame_free(&enc->frame);
148 av_packet_free(&enc->packet);
149 avcodec_free_context(&enc->codec_ctx);
150 SAFE_FREE(enc);
151 return NULL;
152 }
153
154 log_info("FFmpeg HEVC encoder created: %ux%u (codec: %s)", initial_width, initial_height, codec->name);
155
156 // Register encoder for debugging
157 NAMED_REGISTER_H265_ENCODER(enc, "h265_encoder", NULL);
158
159 return enc;
160}
void h265_encoder_t
#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
#define NAMED_REGISTER_H265_ENCODER(encoder, name, parent_ptr)
Register an H.265 encoder with automatic format specifier.
@ ERROR_MEMORY
Definition error_codes.h:56
@ ERROR_MEDIA_INIT
Definition error_codes.h:70
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574

References ERROR_MEDIA_INIT, ERROR_MEMORY, log_info, log_warn, NAMED_REGISTER_H265_ENCODER, SAFE_CALLOC, SAFE_FREE, SAFE_MALLOC, and SET_ERRNO.

◆ h265_encoder_destroy()

void h265_encoder_destroy ( h265_encoder_t *  encoder)

Destroy an x265 encoder

Parameters
encoderEncoder to destroy (may be NULL)

Definition at line 162 of file encoder.c.

162 {
163 if (!encoder)
164 return;
165
166 if (encoder->frame) {
167 av_frame_free(&encoder->frame);
168 }
169 if (encoder->packet) {
170 av_packet_free(&encoder->packet);
171 }
172 if (encoder->codec_ctx) {
173 // Capture x265 encoder cleanup output (final frame stats, etc.)
174 LOG_IO("hevc", { avcodec_free_context(&encoder->codec_ctx); });
175 }
176 if (encoder->yuv_buf) {
177 SAFE_FREE(encoder->yuv_buf);
178 }
179 SAFE_FREE(encoder);
180}
#define LOG_IO(prefix, block)
Capture output from a code block and log it.
Definition io.h:81

References LOG_IO, and SAFE_FREE.

◆ h265_encoder_flush()

asciichat_error_t h265_encoder_flush ( h265_encoder_t *  encoder,
uint8_t *  output_buf,
size_t *  output_size 
)

Flush remaining buffered frames from encoder

Sends a NULL frame to the encoder to flush all buffered frames. This should be called before destroying the encoder or when you need to ensure all encoded data is retrieved.

Parameters
encoderEncoder handle
output_bufBuffer for encoded output
output_sizeInput: buffer size, Output: actual encoded size
Returns
Error code (ASCIICHAT_OK on success, ASCIICHAT_OK with size=0 when no more data)

USAGE: // After final h265_encode() call, flush remaining frames while (size > 0) { size = max_size; h265_encoder_flush(encoder, buf, &size); if (size > 0) { // Send the flushed frame... } }

Definition at line 394 of file encoder.c.

394 {
395 if (!encoder || !output_buf || !output_size) {
396 return SET_ERRNO(ERROR_INTERNAL, "Invalid encoder or output parameters for flush");
397 }
398
399 if (*output_size < 5) {
400 return SET_ERRNO(ERROR_NETWORK_SIZE, "Output buffer too small (minimum 5 bytes)");
401 }
402
403 // Send NULL frame to signal end of stream (flushes encoder)
404 log_debug("H265_FLUSH: Sending NULL frame to encoder to flush buffered data");
405 int send_ret = avcodec_send_frame(encoder->codec_ctx, NULL);
406 if (send_ret < 0) {
407 log_error("H265_FLUSH: avcodec_send_frame(NULL) failed: %d", send_ret);
408 return SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to flush encoder");
409 }
410
411 // Try to receive a packet
412 log_debug("H265_FLUSH: Attempting to receive flushed packet...");
413 int recv_ret = avcodec_receive_packet(encoder->codec_ctx, encoder->packet);
414 if (recv_ret == AVERROR(EAGAIN)) {
415 log_dev("H265_FLUSH: EAGAIN - no more frames available");
416 *output_size = 0;
417 return ASCIICHAT_OK;
418 }
419 if (recv_ret == AVERROR_EOF) {
420 log_debug("H265_FLUSH: EOF - encoder is flushed");
421 *output_size = 0;
422 return ASCIICHAT_OK;
423 }
424 if (recv_ret < 0) {
425 log_error("H265_FLUSH: avcodec_receive_packet failed: %d", recv_ret);
426 return SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to receive flushed packet");
427 }
428
429 log_debug("H265_FLUSH: Got flushed packet: size=%d", (int)encoder->packet->size);
430
431 // Check if packet size exceeds output buffer
432 size_t header_size = 5;
433 size_t packet_size = encoder->packet->size;
434 size_t required_size = header_size + packet_size;
435
436 if (required_size > *output_size) {
437 return SET_ERRNO(ERROR_NETWORK_SIZE, "Output buffer too small: need %zu, have %zu", required_size, *output_size);
438 }
439
440 // Write header: [flags][width:u16][height:u16]
441 uint8_t flags = 0;
442 if (encoder->frame->pict_type == AV_PICTURE_TYPE_I) {
444 encoder->keyframes++;
445 }
446
447 output_buf[0] = flags;
448 output_buf[1] = (encoder->current_width >> 8) & 0xFF;
449 output_buf[2] = encoder->current_width & 0xFF;
450 output_buf[3] = (encoder->current_height >> 8) & 0xFF;
451 output_buf[4] = encoder->current_height & 0xFF;
452
453 // Write encoded data
454 if (packet_size > 0) {
455 memcpy(output_buf + header_size, encoder->packet->data, packet_size);
456 }
457
458 *output_size = required_size;
459 encoder->total_frames++;
460
461 return ASCIICHAT_OK;
462}

References ASCIICHAT_OK, EAGAIN, ERROR_INTERNAL, ERROR_MEDIA_DECODE, ERROR_NETWORK_SIZE, H265_ENCODER_FLAG_KEYFRAME, log_debug, log_dev, log_error, and SET_ERRNO.

◆ h265_encoder_get_stats()

void h265_encoder_get_stats ( h265_encoder_t *  encoder,
uint64_t *  total_frames,
uint64_t *  keyframes,
uint32_t *  avg_bitrate 
)

Get encoder statistics

Parameters
encoderEncoder handle
total_framesOutput: total frames encoded
keyframesOutput: total keyframes encoded
avg_bitrateOutput: average bitrate in bits per second

Definition at line 464 of file encoder.c.

465 {
466 if (!encoder)
467 return;
468
469 if (total_frames)
470 *total_frames = encoder->total_frames;
471 if (keyframes)
472 *keyframes = encoder->keyframes;
473 if (avg_bitrate)
474 *avg_bitrate = encoder->avg_bitrate;
475}

Referenced by h265_client_get_stats().

◆ h265_encoder_request_keyframe()

void h265_encoder_request_keyframe ( h265_encoder_t *  encoder)

Request a keyframe on the next encode

Forces the encoder to produce an I-frame (keyframe) on the next call to h265_encoder_encode(). Useful for error recovery or stream synchronization.

Parameters
encoderEncoder handle

Definition at line 388 of file encoder.c.

388 {
389 if (encoder) {
390 encoder->request_keyframe = true;
391 }
392}