ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
video/h265/decoder.c
Go to the documentation of this file.
1#include <defer.h>
2#include <ascii-chat/otel.h>
11
13#include <ascii-chat/common.h>
15#include <ascii-chat/log/io.h>
16#include <libplatform/system.h>
17
18#include <libavcodec/avcodec.h>
19#include <libavutil/frame.h>
20
21#include <string.h>
22#include <stdlib.h>
23#include <limits.h>
24
25typedef struct h265_decoder {
26 AVCodecContext *codec_ctx;
27 AVFrame *frame;
28 AVPacket *packet;
29 AVCodecParserContext *parser;
30
33
37
39 ASCII_OTEL_SCOPE(H265_DECODER_CREATE);
41 if (!dec) {
42 SET_ERRNO(ERROR_MEMORY, "Failed to allocate decoder structure");
43 return NULL;
44 }
45
46 defer_named(cleanup, h265_decoder_destroy(dec));
47
48 // Find HEVC decoder
49 const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_HEVC);
50 if (!codec) {
51 SET_ERRNO(ERROR_MEDIA_INIT, "HEVC decoder not found");
52 return NULL;
53 }
54
55 dec->codec_ctx = avcodec_alloc_context3(codec);
56 if (!dec->codec_ctx) {
57 SET_ERRNO(ERROR_MEMORY, "Failed to allocate codec context");
58 return NULL;
59 }
60
61 // Open decoder (capture x265 library output during initialization)
62 int codec_open_result = 0;
63 LOG_IO("hevc", { codec_open_result = avcodec_open2(dec->codec_ctx, codec, NULL); });
64
65 if (codec_open_result < 0) {
66 SET_ERRNO(ERROR_MEDIA_INIT, "Failed to open HEVC decoder");
67 return NULL;
68 }
69
70 // Allocate frame and packet
71 dec->frame = av_frame_alloc();
72 dec->packet = av_packet_alloc();
73 dec->parser = av_parser_init(AV_CODEC_ID_HEVC);
74
75 if (!dec->frame || !dec->packet || !dec->parser) {
76 SET_ERRNO(ERROR_MEMORY, "Failed to allocate frame/packet/parser");
77 return NULL;
78 }
79
80 log_info("FFmpeg HEVC decoder created");
81
82 // Register decoder for debugging
83 NAMED_REGISTER_H265_DECODER(dec, "h265_decoder", NULL);
84
85 defer_cancel(cleanup);
86 return dec;
87}
88
90 if (!decoder)
91 return;
92
93 if (decoder->frame) {
94 av_frame_free(&decoder->frame);
95 }
96 if (decoder->packet) {
97 av_packet_free(&decoder->packet);
98 }
99 if (decoder->parser) {
100 av_parser_close(decoder->parser);
101 }
102 if (decoder->codec_ctx) {
103 avcodec_free_context(&decoder->codec_ctx);
104 }
105 SAFE_FREE(decoder);
106}
107
108asciichat_error_t h265_decode(h265_decoder_t *decoder, const uint8_t *encoded_packet, size_t packet_size,
109 uint16_t *output_width, uint16_t *output_height, uint8_t *output_buf,
110 size_t *output_size) {
111 ASCII_OTEL_SCOPE(H265_DECODE);
112 if (!decoder || !encoded_packet || !output_width || !output_height || !output_buf || !output_size) {
113 return SET_ERRNO(ERROR_INTERNAL, "Invalid decoder arguments");
114 }
115
116 if (packet_size < 5) {
117 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Packet too small (minimum 5 bytes)");
118 }
119
120 // Parse header
121 uint8_t flags = encoded_packet[0];
122 uint16_t width = ((uint16_t)encoded_packet[1] << 8) | encoded_packet[2];
123 uint16_t height = ((uint16_t)encoded_packet[3] << 8) | encoded_packet[4];
124
125 if (width == 0 || height == 0) {
126 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid frame dimensions: %ux%u", width, height);
127 }
128
129 size_t required_output = width * height;
130 if (*output_size < required_output) {
131 *output_size = required_output;
132 return SET_ERRNO(ERROR_NETWORK_SIZE, "Output buffer too small: need %zu, have %zu", required_output, *output_size);
133 }
134
135 // Decode HEVC data
136 if (packet_size > 5) {
137 const uint8_t *hevc_data = encoded_packet + 5;
138 size_t hevc_size = packet_size - 5;
139
140 // Send packet to decoder
141 // FFmpeg owns packet storage and requires zero padding after the payload.
142 // The wire buffer belongs to the caller and may be stack allocated.
143 if (hevc_size > INT_MAX)
144 return SET_ERRNO(ERROR_NETWORK_SIZE, "Encoded video packet exceeds FFmpeg limits");
145 av_packet_unref(decoder->packet);
146 if (av_new_packet(decoder->packet, (int)hevc_size) < 0)
147 return SET_ERRNO(ERROR_MEMORY, "Cannot allocate decoder packet");
148 memcpy(decoder->packet->data, hevc_data, hevc_size);
149
150 uint64_t stats_codec_start = time_get_ns();
151 int send_result = avcodec_send_packet(decoder->codec_ctx, decoder->packet);
152 av_packet_unref(decoder->packet);
153 if (send_result < 0) {
154 return SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to send packet to decoder");
155 }
156
157 int stats_decode_result = avcodec_receive_frame(decoder->codec_ctx, decoder->frame);
158 stats_duration_record(stats_runtime_scope(), STATS_DURATION_DECODE, time_get_ns() - stats_codec_start);
159 if (stats_decode_result < 0) {
160 return SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to receive decoded frame");
161 }
162 } else {
163 // Empty packet - just signal a frame boundary
164 if (avcodec_send_packet(decoder->codec_ctx, NULL) < 0) {
165 return SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to flush decoder");
166 }
167 }
168
169 // Extract Y plane (grayscale intensity)
170 uint8_t *y_plane = decoder->frame->data[0];
171 int y_stride = decoder->frame->linesize[0];
172 int img_width = decoder->frame->width;
173 int img_height = decoder->frame->height;
174
175 if (img_width != (int)width || img_height != (int)height) {
176 log_warn("Image dimensions mismatch: expected %ux%u, got %dx%d", width, height, img_width, img_height);
177 }
178
179 // Copy Y plane to output (ASCII intensity values)
180 for (int y = 0; y < img_height && y < (int)height; y++) {
181 memcpy(output_buf + y * width, y_plane + y * y_stride, width);
182 }
183
184 *output_width = width;
185 *output_height = height;
186 *output_size = width * height;
187
188 decoder->last_width = width;
189 decoder->last_height = height;
190 decoder->total_frames++;
191 ASCII_OTEL_ATTR_STRING("video.codec", "h265");
192 ASCII_OTEL_ATTR_INT("video.frame.width", width);
193 ASCII_OTEL_ATTR_INT("video.frame.height", height);
194 ASCII_OTEL_ATTR_INT("video.frame.keyframe", (flags & H265_DECODER_FLAG_KEYFRAME) != 0);
195 ASCII_OTEL_ATTR_INT("video.packet.size", (int64_t)packet_size);
196 ascii_otel_metric(ASCII_OTEL_METRIC_VIDEO_DECODE_BYTES, (double)packet_size);
197 ascii_otel_metric(ASCII_OTEL_METRIC_VIDEO_DECODE_FRAMES, 1);
198 ascii_otel_metric(ASCII_OTEL_METRIC_VIDEO_DECODE_BYTES_OUT, (double)*output_size);
200
201 if (flags & H265_DECODER_FLAG_KEYFRAME) {
202 decoder->keyframes++;
203 }
204
205 return ASCIICHAT_OK;
206}
207
208void h265_decoder_get_stats(h265_decoder_t *decoder, uint64_t *total_frames, uint64_t *keyframes, uint16_t *last_width,
209 uint16_t *last_height) {
210 if (!decoder)
211 return;
212
213 if (total_frames)
214 *total_frames = decoder->total_frames;
215 if (keyframes)
216 *keyframes = decoder->keyframes;
217 if (last_width)
218 *last_width = decoder->last_width;
219 if (last_height)
220 *last_height = decoder->last_height;
221}
#define SET_ERRNO(code,...)
⚙️ Common definitions, error codes, macros, and types shared throughout the application
Named object registry for debugging — log identifiable resource names.
unsigned int height
Definition fft.c:23
unsigned int width
Definition fft.c:23
unsigned short uint16_t
Definition common.h:61
#define SAFE_FREE(ptr)
Definition common.h:377
#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_DECODER(decoder, name, parent_ptr)
Register an H.265 decoder with automatic format specifier.
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_NETWORK_PROTOCOL
Definition error_codes.h:76
@ 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_INTERNAL
Definition error_codes.h:87
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
#define log_info(...)
Log an INFO message.
Definition log/log.h:608
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)
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
AVCodecContext * codec_ctx
AVCodecParserContext * parser
void h265_decoder_destroy(h265_decoder_t *decoder)
struct h265_decoder h265_decoder_t
h265_decoder_t * h265_decoder_create(void)
asciichat_error_t h265_decode(h265_decoder_t *decoder, const uint8_t *encoded_packet, size_t packet_size, uint16_t *output_width, uint16_t *output_height, uint8_t *output_buf, size_t *output_size)
void h265_decoder_get_stats(h265_decoder_t *decoder, uint64_t *total_frames, uint64_t *keyframes, uint16_t *last_width, uint16_t *last_height)
HEVC/H.265 decoder for ASCII art frames using libde265.
#define H265_DECODER_FLAG_KEYFRAME