ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
opus.c
Go to the documentation of this file.
1
6
7#include <ascii-chat/otel.h>
10#include <ascii-chat/common.h>
13#include <opus.h>
14#include <stdlib.h>
15#include <string.h>
16
17/* ============================================================================
18 * Encoder Creation
19 * ============================================================================ */
20
22 if (!encoder || bitrate < 6000 || bitrate > 510000)
23 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid Opus encoder or bitrate: %d", bitrate);
24 int result = opus_encoder_ctl(encoder, OPUS_SET_BITRATE(bitrate));
25 if (result == OPUS_OK)
26 result = opus_encoder_ctl(encoder, OPUS_SET_VBR(1));
27 // Ordered streams retain silence packets so playout timing remains continuous.
28 if (result == OPUS_OK)
29 result = opus_encoder_ctl(encoder, OPUS_SET_DTX(0));
30 if (result != OPUS_OK)
31 return SET_ERRNO(ERROR_AUDIO, "Opus configuration failed: %s", opus_strerror(result));
32 return ASCIICHAT_OK;
33}
34
35opus_codec_t *opus_codec_create_encoder(opus_application_t application, int sample_rate, int bitrate) {
36 return opus_codec_create_encoder_channels(application, sample_rate, bitrate, AUDIO_CHANNELS);
37}
38
39opus_codec_t *opus_codec_create_encoder_channels(opus_application_t application, int sample_rate, int bitrate,
40 int channels) {
41 ASCII_OTEL_SCOPE(OPUS_ENCODER_CREATE);
42 ASCII_OTEL_ATTR_INT("audio.sample_rate", sample_rate);
43 ASCII_OTEL_ATTR_INT("audio.bitrate", bitrate);
44 if (sample_rate <= 0 || bitrate <= 0 || (channels != 1 && channels != 2)) {
45 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid codec parameters: sample_rate=%d, bitrate=%d", sample_rate, bitrate);
46 return NULL;
47 }
48
50 if (!codec) {
51 SET_ERRNO(ERROR_MEMORY, "Failed to allocate opus codec context");
52 return NULL;
53 }
54
55 // Create encoder
56 int error = OPUS_OK;
57 codec->encoder = opus_encoder_create(sample_rate, channels, (int)application, &error);
58 if (error != OPUS_OK || !codec->encoder) {
59 SET_ERRNO(ERROR_AUDIO, "Failed to create Opus encoder: %s", opus_strerror(error));
60 SAFE_FREE(codec);
61 return NULL;
62 }
63
64 if (audio_opus_configure_encoder(codec->encoder, bitrate) != ASCIICHAT_OK) {
65 opus_encoder_destroy(codec->encoder);
66 SAFE_FREE(codec);
67 return NULL;
68 }
69
70 codec->decoder = NULL;
71 codec->sample_rate = sample_rate;
72 codec->channels = channels;
73 codec->bitrate = bitrate;
74 codec->tmp_buffer = NULL;
75
76 log_debug("Opus encoder created: sample_rate=%d, bitrate=%d bps, continuous VBR", sample_rate, bitrate);
77
78 // Register Opus encoder for debugging
79 NAMED_REGISTER_CONTEXT(codec, "opus_encoder", "opus_codec", NULL);
80
81 return codec;
82}
83
84/* ============================================================================
85 * Decoder Creation
86 * ============================================================================ */
87
91
92opus_codec_t *opus_codec_create_decoder_channels(int sample_rate, int channels) {
93 ASCII_OTEL_SCOPE(OPUS_DECODER_CREATE);
94 ASCII_OTEL_ATTR_INT("audio.sample_rate", sample_rate);
95 if (sample_rate <= 0 || (channels != 1 && channels != 2)) {
96 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid sample rate: %d", sample_rate);
97 return NULL;
98 }
99
101 if (!codec) {
102 SET_ERRNO(ERROR_MEMORY, "Failed to allocate opus codec context");
103 return NULL;
104 }
105
106 // Create decoder
107 int error = OPUS_OK;
108 codec->decoder = opus_decoder_create(sample_rate, channels, &error);
109 if (error != OPUS_OK || !codec->decoder) {
110 SET_ERRNO(ERROR_AUDIO, "Failed to create Opus decoder: %s", opus_strerror(error));
111 SAFE_FREE(codec);
112 return NULL;
113 }
114
115 codec->encoder = NULL;
116 codec->sample_rate = sample_rate;
117 codec->channels = channels;
118 codec->bitrate = 0; // N/A for decoder
119 codec->tmp_buffer = NULL;
120
121 log_debug("Opus decoder created: sample_rate=%d", sample_rate);
122
123 // Register Opus decoder for debugging
124 NAMED_REGISTER_CONTEXT(codec, "opus_decoder", "opus_codec", NULL);
125
126 return codec;
127}
128
129/* ============================================================================
130 * Encoding
131 * ============================================================================ */
132
133size_t opus_codec_encode(opus_codec_t *codec, const float *samples, int num_samples, uint8_t *out_data,
134 size_t out_size) {
135 ASCII_OTEL_SCOPE(OPUS_CODEC_ENCODE);
136 ASCII_OTEL_ATTR_STRING("audio.codec", "opus");
137 ASCII_OTEL_ATTR_INT("audio.frame.samples", num_samples);
138 ASCII_OTEL_ATTR_INT("audio.output.capacity", (int64_t)out_size);
139 if (!codec || !codec->encoder || !samples || num_samples <= 0 || !out_data || out_size == 0 ||
140 num_samples % codec->channels != 0) {
142 "Invalid encode parameters: codec=%p, encoder=%p, samples=%p, num_samples=%d, out_data=%p, "
143 "out_size=%zu",
144 (void *)codec, (void *)(codec ? codec->encoder : NULL), (const void *)samples, num_samples,
145 (void *)out_data, out_size);
146 return 0;
147 }
148
149 ASCII_OTEL_ATTR_INT("audio.sample_rate", codec->sample_rate);
150 ASCII_OTEL_ATTR_INT("audio.bitrate", codec->bitrate);
151 ASCII_OTEL_ATTR_INT("audio.channels", codec->channels);
152 // Encode frame
153 opus_int32 encoded_bytes =
154 opus_encode_float(codec->encoder, samples, num_samples / codec->channels, out_data, (opus_int32)out_size);
155
156 if (encoded_bytes < 0) {
157 SET_ERRNO(ERROR_AUDIO, "Opus encoding failed: %s", opus_strerror((int)encoded_bytes));
158 return 0;
159 }
160
161 if (encoded_bytes == 0) {
162 // DTX frame (silence) - encoder produced zero bytes
163 log_debug_every(LOG_RATE_VERY_FAST, "Opus DTX frame (silence detected)");
164 }
165
166 ascii_otel_metric(ASCII_OTEL_METRIC_OPUS_BYTES, encoded_bytes);
167 ascii_otel_metric(ASCII_OTEL_METRIC_OPUS_ENCODE_SAMPLES, num_samples);
168 ASCII_OTEL_ATTR_INT("audio.packet.size", encoded_bytes);
169 return (size_t)encoded_bytes;
170}
171
172/* ============================================================================
173 * Decoding
174 * ============================================================================ */
175
176int opus_codec_decode(opus_codec_t *codec, const uint8_t *data, size_t data_len, float *out_samples,
177 int out_num_samples) {
178 ASCII_OTEL_SCOPE(OPUS_CODEC_DECODE);
179 ASCII_OTEL_ATTR_STRING("audio.codec", "opus");
180 ASCII_OTEL_ATTR_INT("audio.packet.size", (int64_t)data_len);
181 ASCII_OTEL_ATTR_INT("audio.packet_loss_concealment", !data || !data_len);
182 if (!codec || !codec->decoder || !out_samples || out_num_samples <= 0 || out_num_samples % codec->channels != 0) {
184 "Invalid decode parameters: codec=%p, decoder=%p, out_samples=%p, out_num_samples=%d", (void *)codec,
185 (void *)(codec ? codec->decoder : NULL), (void *)out_samples, out_num_samples);
186 return -1;
187 }
188
189 ASCII_OTEL_ATTR_INT("audio.sample_rate", codec->sample_rate);
190 ASCII_OTEL_ATTR_INT("audio.channels", codec->channels);
191 ASCII_OTEL_ATTR_INT("audio.output.capacity_samples", out_num_samples);
192 // If data is NULL, use PLC (Packet Loss Concealment)
193 if (!data || data_len == 0) {
194 log_debug_every(LOG_RATE_VERY_FAST, "Opus PLC (Packet Loss Concealment)");
195 int samples = opus_decode_float(codec->decoder, NULL, 0, out_samples, out_num_samples / codec->channels, 0);
196 if (samples < 0) {
197 SET_ERRNO(ERROR_AUDIO, "Opus PLC failed: %s", opus_strerror(samples));
198 return -1;
199 }
200 ascii_otel_metric(ASCII_OTEL_METRIC_OPUS_DECODE_SAMPLES, samples);
201 ascii_otel_metric(ASCII_OTEL_METRIC_OPUS_PLC_FRAMES, 1);
202 ASCII_OTEL_ATTR_INT("audio.frame.samples", samples);
203 return samples * codec->channels;
204 }
205
206 // Decode frame
207 int samples =
208 opus_decode_float(codec->decoder, data, (opus_int32)data_len, out_samples, out_num_samples / codec->channels, 0);
209
210 if (samples < 0) {
211 SET_ERRNO(ERROR_AUDIO, "Opus decoding failed: %s", opus_strerror(samples));
212 return -1;
213 }
214
215 ascii_otel_metric(ASCII_OTEL_METRIC_OPUS_DECODE_BYTES, (double)data_len);
216 ASCII_OTEL_ATTR_INT("audio.frame.samples", samples);
217 ascii_otel_metric(ASCII_OTEL_METRIC_OPUS_DECODE_SAMPLES, samples);
218 return samples * codec->channels;
219}
220
221/* ============================================================================
222 * Configuration
223 * ============================================================================ */
224
226 ASCII_OTEL_SCOPE(OPUS_CODEC_SET_BITRATE);
227 if (!codec || !codec->encoder || bitrate <= 0) {
228 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid bitrate parameters: codec=%p, encoder=%p, bitrate=%d", (void *)codec,
229 (void *)(codec ? codec->encoder : NULL), bitrate);
230 }
231
232 int error = opus_encoder_ctl(codec->encoder, OPUS_SET_BITRATE(bitrate));
233 if (error != OPUS_OK) {
234 return SET_ERRNO(ERROR_AUDIO, "Failed to set Opus bitrate: %s", opus_strerror(error));
235 }
236
237 codec->bitrate = bitrate;
238 log_debug("Opus bitrate changed to %d bps", bitrate);
239
240 return ASCIICHAT_OK;
241}
242
244 if (!codec || !codec->encoder) {
245 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid codec for bitrate query");
246 return -1;
247 }
248
249 opus_int32 bitrate = 0;
250 int error = opus_encoder_ctl(codec->encoder, OPUS_GET_BITRATE(&bitrate));
251 if (error != OPUS_OK) {
252 SET_ERRNO(ERROR_AUDIO, "Failed to get Opus bitrate: %s", opus_strerror(error));
253 return -1;
254 }
255
256 return (int)bitrate;
257}
258
260 ASCII_OTEL_SCOPE(OPUS_DTX);
261 ASCII_OTEL_ATTR_INT("audio.dtx.enabled", enable != 0);
262 if (!codec || !codec->encoder) {
263 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid codec for DTX configuration");
264 }
265
266 int error = opus_encoder_ctl(codec->encoder, OPUS_SET_DTX(enable ? 1 : 0));
267 if (error != OPUS_OK) {
268 return SET_ERRNO(ERROR_AUDIO, "Failed to set Opus DTX: %s", opus_strerror(error));
269 }
270
271 log_debug("Opus DTX %s", enable ? "enabled" : "disabled");
272
273 return ASCIICHAT_OK;
274}
275
276/* ============================================================================
277 * Lifecycle
278 * ============================================================================ */
279
281 if (!codec) {
282 return;
283 }
284
285 NAMED_UNREGISTER(codec);
286
287 if (codec->encoder) {
288 opus_encoder_destroy(codec->encoder);
289 codec->encoder = NULL;
290 }
291
292 if (codec->decoder) {
293 opus_decoder_destroy(codec->decoder);
294 codec->decoder = NULL;
295 }
296
297 if (codec->tmp_buffer) {
298 SAFE_FREE(codec->tmp_buffer);
299 codec->tmp_buffer = NULL;
300 }
301
302 SAFE_FREE(codec);
303}
Synchronized per-thread pending error stacks and value snapshots. SET_ERRNO pushes,...
#define SET_ERRNO(code,...)
#define AUDIO_CHANNELS
Definition audio/limits.h:5
⚙️ Common definitions, error codes, macros, and types shared throughout the application
Named object registry for debugging — log identifiable resource names.
opus_codec_t * opus_codec_create_decoder(int sample_rate)
Create an Opus decoder.
Definition opus.c:88
asciichat_error_t opus_codec_set_bitrate(opus_codec_t *codec, int bitrate)
Set encoder bitrate.
Definition opus.c:225
asciichat_error_t audio_opus_configure_encoder(OpusEncoder *encoder, int bitrate)
Definition opus.c:21
opus_application_t
State for one Opus encoding or decoding direction.
Definition opus.h:84
int opus_codec_decode(opus_codec_t *codec, const uint8_t *data, size_t data_len, float *out_samples, int out_num_samples)
Decode Opus audio frame.
Definition opus.c:176
opus_codec_t * opus_codec_create_encoder(opus_application_t application, int sample_rate, int bitrate)
Create an Opus encoder.
Definition opus.c:35
int opus_codec_get_bitrate(opus_codec_t *codec)
Get current encoder bitrate.
Definition opus.c:243
asciichat_error_t opus_codec_set_dtx(opus_codec_t *codec, int enable)
Enable/disable DTX (Discontinuous Transmission).
Definition opus.c:259
void opus_codec_destroy(opus_codec_t *codec)
Destroy an Opus codec instance.
Definition opus.c:280
size_t opus_codec_encode(opus_codec_t *codec, const float *samples, int num_samples, uint8_t *out_data, size_t out_size)
Encode audio frame with Opus.
Definition opus.c:133
opus_codec_t * opus_codec_create_decoder_channels(int sample_rate, int channels)
Definition opus.c:92
struct OpusEncoder OpusEncoder
opus_codec_t * opus_codec_create_encoder_channels(opus_application_t application, int sample_rate, int bitrate, int channels)
Definition opus.c:39
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_MALLOC(size, cast)
Definition common.h:265
unsigned char uint8_t
Definition common.h:60
#define NAMED_REGISTER_CONTEXT(context, context_type, name, parent_ptr)
Register a generic context with automatic format specifier.
#define NAMED_UNREGISTER(ptr)
Unregister a pointer.
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_AUDIO
Definition error_codes.h:62
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
#define LOG_RATE_VERY_FAST
Log rate limit: 0.1 seconds (100,000 microseconds) - for very high frequency operations.
Definition log_rates.h:23
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:749
Opus audio codec wrapper for real-time encoding/decoding.
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
🔤 String Manipulation and Shell Escaping Utilities
State for one Opus encoding or decoding direction.
Definition opus.h:105
uint8_t * tmp_buffer
Temporary buffer for internal use.
Definition opus.h:111
int bitrate
Bitrate in bits per second (encoder only).
Definition opus.h:110
int channels
PCM channels (1 or 2); sample counts include all channels.
Definition opus.h:109
int sample_rate
Sample rate in Hz (e.g., 44100).
Definition opus.h:108
OpusEncoder * encoder
Encoder state (NULL if decoder).
Definition opus.h:106
OpusDecoder * decoder
Decoder state (NULL if encoder).
Definition opus.h:107