Asterisk - The Open Source Telephony Project GIT-master-4c84066
Loading...
Searching...
No Matches
test_codec_translations.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2026, Asterisk Community
5 *
6 * See http://www.asterisk.org for more information about
7 * the Asterisk project. Please do not directly contact
8 * any of the maintainers of this project for assistance;
9 * the project provides a web site, mailing lists and IRC
10 * channels for your use.
11 *
12 * This program is free software, distributed under the terms of
13 * the GNU General Public License Version 2. See the LICENSE file
14 * at the top of the source tree.
15 */
16
17/*!
18 * \file
19 * \brief Codec Translation Roundtrip Tests
20 *
21 * \author Sebastian Jennen <sebastian.t.jennen@gmail.com>
22 *
23 * Tests that encoding sample frames through a codec and decoding them
24 * back to slin produces output that is (almost) identical to
25 * the original input. This verifies that each codec translator pair
26 * (slin -> codec -> slin) does not corrupt or destroy the audio signal.
27 *
28 * For near-lossless codecs (alaw, ulaw) the tolerance is very tight.
29 * For lossy codecs (adpcm, g726, gsm, g722, speex, ilbc, opus, g729, etc.)
30 * a Signal-to-Noise Ratio (SNR) threshold is used since some degradation
31 * is inherent to the compression algorithm. Wideband codecs are tested
32 * with a matching slin16/slin32/slin48 signal at the codec's native
33 * sample rate.
34 */
35
36/*** MODULEINFO
37 <depend>TEST_FRAMEWORK</depend>
38 <support_level>extended</support_level>
39 ***/
40
41#include "asterisk.h"
42
43#include <math.h>
44
45#include "asterisk/module.h"
46#include "asterisk/test.h"
47#include "asterisk/translate.h"
48#include "asterisk/format.h"
49#include "asterisk/format_cap.h"
51#include "asterisk/codec.h"
52#include "asterisk/frame.h"
53#include "asterisk/slin.h"
54#include "asterisk/logger.h"
55#include "asterisk/utils.h"
56
57/*! Duration of the test signal in seconds */
58#define TEST_DURATION_SECS 2
59
60/*! Chunk duration in milliseconds to feed chunks to the coders */
61#define CHUNK_MS 20
62
63/*! Minimum acceptable SNR in dB for lossy codecs.
64 * Most telephony codecs at 8 kHz should exceed ~20 dB */
65#define MIN_SNR_LOSSY_DB 15.0
66
67/*! Maximum per-sample absolute error for near-lossless codecs (ulaw/alaw).
68 * μ-law/A-law quantisation can reach a max error of 256. */
69#define MAX_SAMPLE_ERR_LOSSLESS 256
70
71/*! Minimum fraction of input samples that must survive the roundtrip.
72 * Codec lookahead + trailing partial frame may consume some samples;
73 * require at least 90 % to call the test valid. */
74#define MIN_DECODED_RATIO 0.90
75
76/*!
77 * \brief Generate a synthetic speech-like test signal in linear sample.
78 *
79 * a mix of a 200 Hz fundamental and an 800 Hz harmonic with a slow 4 Hz
80 * amplitude-modulation envelope, loosely inspired by ITU-T P.50 artificial
81 * voice.
82 *
83 * \param[out] buf Buffer to fill (must hold at least \a samples int16 values)
84 * \param[in] samples Number of samples to generate
85 * \param[in] sample_rate Sampling rate in Hz (used to scale time correctly)
86 */
87static void generate_speech_signal(int16_t *buf, int samples, int sample_rate)
88{
89 int i;
90 for (i = 0; i < samples; i++) {
91 double t = (double)i / sample_rate;
92 double sig = 0.6 * sin(2.0 * M_PI * 200.0 * t)
93 + 0.4 * sin(2.0 * M_PI * 800.0 * t);
94 /* Slow AM at 4 Hz to simulate syllable rhythm */
95 sig *= 0.5 * (1.0 + sin(2.0 * M_PI * 4.0 * t));
96 buf[i] = (int16_t)(sig * 16000.0);
97 }
98}
99
100/*!
101 * \brief Compute Signal-to-Noise Ratio between original and roundtripped signal.
102 *
103 * \param[in] orig Original sample buffer
104 * \param[in] roundtrip Decoded (roundtripped) sample buffer
105 * \param[in] samples Number of samples
106 *
107 * \return SNR in dB
108 */
109static double compute_snr(const int16_t *orig, const int16_t *roundtrip, int samples)
110{
111 double signal_power = 0.0;
112 double noise_power = 0.0;
113 int i;
114
115 for (i = 0; i < samples; i++) {
116 double s = (double)orig[i];
117 double n = (double)(orig[i] - roundtrip[i]);
118 signal_power += s * s;
119 noise_power += n * n;
120 }
121
122 if (signal_power < 1.0) {
123 return -100.0; /* degenerate signal */
124 }
125 if (noise_power < 1.0) {
126 return 999.0; /* essentially perfect */
127 }
128
129 return 10.0 * log10(signal_power / noise_power);
130}
131
132/*!
133 * \brief Compute SNR after aligning the decoded signal via cross-correlation.
134 *
135 * Some codecs (e.g. speex, opus) introduce an algorithmic lookahead delay:
136 * the decoded signal is time-shifted by a fixed number of samples relative to
137 * the original. Computing SNR without compensating for this shift yields a
138 * near-zero result even when the codec works perfectly.
139 *
140 * This function searches delays d = 0..max_delay, finds the integer shift
141 * that maximises the cross-correlation sum orig[i]·decoded[i+d], then returns
142 * the SNR computed at that optimal alignment.
143 *
144 * \param[in] orig Original sample buffer (must hold at least \a samples values)
145 * \param[in] decoded Decoded sample buffer (must hold at least \a samples values)
146 * \param[in] samples Number of samples available in each buffer
147 * \param[in] max_delay Search range: delays 0..max_delay are tested
148 * \param[out] delay_out Receives the best-fit delay found; may be NULL
149 *
150 * \return SNR in dB at the best-fit alignment, or the unaligned SNR when
151 * \a samples <= \a max_delay
152 */
153static double compute_snr_aligned(const int16_t *orig, const int16_t *decoded,
154 int samples, int max_delay, int *delay_out)
155{
156 double best_corr = -1e300;
157 int best_delay = 0;
158 int d;
159
160 if (delay_out) {
161 *delay_out = 0;
162 }
163
164 /* Need enough samples for a meaningful search */
165 if (samples <= max_delay || max_delay <= 0) {
166 return compute_snr(orig, decoded, samples);
167 }
168
169 for (d = 0; d <= max_delay; d++) {
170 int n = samples - d;
171 double corr = 0.0;
172 int i;
173 for (i = 0; i < n; i++) {
174 corr += (double)orig[i] * (double)decoded[i + d];
175 }
176 if (corr > best_corr) {
177 best_corr = corr;
178 best_delay = d;
179 }
180 }
181
182 if (delay_out) {
183 *delay_out = best_delay;
184 }
185
186 /* Compute SNR at the best alignment */
187 return compute_snr(orig, decoded + best_delay, samples - best_delay);
188}
189
190/*!
191 * \brief Compute maximum absolute per-sample error.
192 */
193static int compute_max_error(const int16_t *orig, const int16_t *roundtrip, int samples)
194{
195 int max_err = 0;
196 int i;
197
198 for (i = 0; i < samples; i++) {
199 int err = abs((int)orig[i] - (int)roundtrip[i]);
200 if (err > max_err) {
201 max_err = err;
202 }
203 }
204 return max_err;
205}
206
207/*!
208 * \brief Attempt a roundtrip encode/decode for one codec format.
209 *
210 * Feeds the original slin signal through the encoder and decoder in 20 ms
211 * chunks (matching the universal VoIP frame duration), accumulates the
212 * decoded output, and compares the full result against the original.
213 * This naturally respects every codec's internal buffer sizes and frame
214 * granularity without needing per-codec sample-count overrides.
215 *
216 * \param[in] test Test framework handle (for status messages)
217 * \param[in] slin_fmt The slin format appropriate for the codec's sample rate
218 * \param[in] target_fmt The codec format to test
219 * \param[in] orig_buf Original slin sample buffer
220 * \param[in] total_samples Number of samples in orig_buf
221 * \param[in] sample_rate Sample rate in Hz
222 * \param[in] is_lossy If non-zero, use SNR-based comparison; otherwise use max-error
223 * \param[in] min_snr_db Minimum acceptable SNR for lossy check (ignored when !is_lossy)
224 *
225 * \retval AST_TEST_PASS on success
226 * \retval AST_TEST_FAIL on failure
227 */
229 struct ast_test *test,
230 struct ast_format *slin_fmt,
231 struct ast_format *target_fmt,
232 const int16_t *orig_buf,
233 int total_samples,
234 int sample_rate,
235 int is_lossy,
236 double min_snr_db)
237{
238 struct ast_trans_pvt *encode_path = NULL;
239 struct ast_trans_pvt *decode_path = NULL;
240 int16_t *decoded_buf = NULL;
241 int total_decoded = 0;
242 int chunk_samples = sample_rate * CHUNK_MS / 1000;
243 int buf_capacity;
244 int offset;
246 const char *codec_name;
247
248 codec_name = ast_format_get_name(target_fmt);
249
250 /* --- Build encoder path: slin -> codec --- */
251 encode_path = ast_translator_build_path(target_fmt, slin_fmt);
252 if (!encode_path) {
254 "Skipping %s: no translation path from slin to %s\n",
255 codec_name, codec_name);
256 return AST_TEST_PASS;
257 }
258
259 /* --- Build decoder path: codec -> slin --- */
260 decode_path = ast_translator_build_path(slin_fmt, target_fmt);
261 if (!decode_path) {
263 "FAIL %s: found encoder but no decoder path back to slin\n",
264 codec_name);
265 goto cleanup;
266 }
267
268 /* Allocate output buffer with some headroom for codec expansion */
269 buf_capacity = total_samples + chunk_samples;
270 decoded_buf = ast_calloc(buf_capacity, sizeof(int16_t));
271 if (!decoded_buf) {
272 goto cleanup;
273 }
274
275 /* --- Feed audio in CHUNK_MS chunks through encode -> decode --- */
276 for (offset = 0; offset < total_samples; offset += chunk_samples) {
277 struct ast_frame input_frame;
278 struct ast_frame *encoded;
279 struct ast_frame *decoded;
280 struct ast_frame *cur;
281 int feed = total_samples - offset;
282 if (feed > chunk_samples) {
283 feed = chunk_samples;
284 }
285
286 memset(&input_frame, 0, sizeof(input_frame));
287 input_frame.frametype = AST_FRAME_VOICE;
288 input_frame.subclass.format = slin_fmt;
289 input_frame.datalen = feed * sizeof(int16_t);
290 input_frame.samples = feed;
291 input_frame.data.ptr = (void *)(orig_buf + offset);
292 input_frame.mallocd = 0;
293 input_frame.src = "test_codec_translations";
294
295 /* Encode: slin -> codec. NULL means the encoder is buffering. */
296 encoded = ast_translate(encode_path, &input_frame, 0);
297 if (!encoded) {
298 continue;
299 }
300
301 /* Decode: codec -> slin. ast_translate follows the frame linked
302 * list internally, so passing the head feeds all encoded frames. */
303 decoded = ast_translate(decode_path, encoded, 0);
304 if (!decoded) {
305 continue;
306 }
307
308 /* Copy decoded samples into our accumulation buffer.
309 * Walk the linked list in case frameout produced multiple frames. */
310 for (cur = decoded; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
311 int copy;
312 if (cur->frametype != AST_FRAME_VOICE || !cur->data.ptr
313 || cur->samples <= 0) {
314 continue;
315 }
316 copy = cur->samples;
317 if (total_decoded + copy > buf_capacity) {
318 copy = buf_capacity - total_decoded;
319 }
320 memcpy(decoded_buf + total_decoded, cur->data.ptr,
321 copy * sizeof(int16_t));
322 total_decoded += copy;
323 }
324 }
325
326 /* --- Verify we got enough decoded audio --- */
327 if (total_decoded < (int)(total_samples * MIN_DECODED_RATIO)) {
329 "FAIL %s: decoded only %d of %d samples (%.0f%%)\n",
330 codec_name, total_decoded, total_samples,
331 100.0 * total_decoded / total_samples);
332 goto cleanup;
333 }
334
335 /* --- Compare original vs decoded --- */
336 {
337 int cmp_samples = total_decoded < total_samples
338 ? total_decoded : total_samples;
339
340 if (is_lossy) {
341 int max_delay = sample_rate * 20 / 1000; /* 20 ms search */
342 int delay = 0;
343 double snr = compute_snr_aligned(orig_buf, decoded_buf,
344 cmp_samples, max_delay, &delay);
345
347 " %s (lossy): SNR = %.1f dB (threshold %.1f dB)"
348 " [%d/%d samples, delay=%d/%.1fms]\n",
349 codec_name, snr, min_snr_db,
350 cmp_samples, total_samples,
351 delay, 1000.0 * delay / sample_rate);
352
353 if (snr < min_snr_db) {
355 "FAIL %s: SNR %.1f dB is below minimum %.1f dB\n",
356 codec_name, snr, min_snr_db);
357 goto cleanup;
358 }
359 } else {
360 int max_err = compute_max_error(orig_buf, decoded_buf, cmp_samples);
361 double snr = compute_snr(orig_buf, decoded_buf, cmp_samples);
362
364 " %s (lossless): max_err = %d (limit %d),"
365 " SNR = %.1f dB [%d/%d samples]\n",
366 codec_name, max_err, MAX_SAMPLE_ERR_LOSSLESS, snr,
367 cmp_samples, total_samples);
368
369 if (max_err > MAX_SAMPLE_ERR_LOSSLESS) {
371 "FAIL %s: max sample error %d exceeds limit %d\n",
372 codec_name, max_err, MAX_SAMPLE_ERR_LOSSLESS);
373 goto cleanup;
374 }
375 }
376 }
377
379
380cleanup:
381 ast_free(decoded_buf);
382 if (encode_path) {
383 ast_translator_free_path(encode_path);
384 }
385 if (decode_path) {
386 ast_translator_free_path(decode_path);
387 }
388
389 return result;
390}
391
392/*!
393 * \brief Codec roundtrip entry: table of codecs to test.
394 */
396 const char *format_name; /*!< Name used in ast_format_cache_get() */
397 int is_lossy; /*!< 1 = lossy (use SNR check), 0 = near-lossless (use max err) */
398 double min_snr_db; /*!< Per-codec SNR floor; 0.0 = use MIN_SNR_LOSSY_DB default */
399 int sample_rate; /*!< Native slin rate: 8000/16000/32000/48000; 0 = default 8000 */
400};
401
402/*! Table of codecs to roundtrip-test.*/
403static const struct codec_test_entry codec_table[] = {
404 /* Near-lossless narrowband (8 kHz) — verified with max per-sample error */
405 { "ulaw", 0 },
406 { "alaw", 0 },
407
408 /* Lossy narrowband (8 kHz) — verified with SNR threshold */
409 { "adpcm", 1 }, /* ADPCM: ~20 dB SNR, lossy by design */
410 { "g726", 1 }, /* G.726 ADPCM: ~28 dB SNR, lossy by design */
411 { "g726aal2", 1 }, /* G.726 AAL2 ADPCM: same as g726 */
412 { "gsm", 1 },
413 { "speex", 1, 7.0 }, /* speex is quite lossy */
414 { "ilbc", 1, 7.0 }, /* 30 ms frames: coarser quantisation lowers SNR floor */
415 { "codec2", 1, -2.0 }, /* vocoder: snr is really low, smoke-test only */
416 { "lpc10", 1, -2.0 }, /* vocoder: snr is really low, smoke-test only */
417
418 /* { "g729", 1 }, UNTESTED yet */
419 /* { "silk8", 1 }, UNTESTED yet */
420 /* { "silk12", 1 }, UNTESTED yet */
421 /* { "silk16", 1 }, UNTESTED yet */
422 /* { "silk24", 1 }, UNTESTED yet */
423
424 /* Wideband (16 kHz) — tested with slin16 signal. */
425 { "g722", 1, 0.0, 16000 },
426 { "speex16", 1, 5.0, 16000 },
427
428 /* Ultra-wideband (32 kHz) — tested with slin32 signal */
429 { "speex32", 1, 5.0, 32000 },
430
431 /* Opus native rate is 48 kHz */
432 { "opus", 1, 8.0, 48000 },
433};
434
435AST_TEST_DEFINE(codec_translate_roundtrip)
436{
437 int i;
438 int tested = 0;
439 int failed = 0;
441
442 switch (cmd) {
443 case TEST_INIT:
444 info->name = "codec_translations";
445 info->category = "/main/codec/";
446 info->summary = "Roundtrip encode/decode test and quality check for various codecs referenced in this test and present in the installation";
447 info->description =
448 "Generates a synthetic speech-like signal (200 Hz +\n"
449 "800 Hz with 4 Hz AM envelope) at the codec's native sample\n"
450 "rate, feeds it through the codec and back,\n"
451 "then verifies the output quality over the full duration.\n"
452 "For near-lossless codecs (ulaw, alaw) it checks that the\n"
453 "maximum per-sample error is within a tight bound.\n"
454 "For lossy codecs it checks that the SNR exceeds a per-codec\n"
455 "minimum threshold. Vocoders use a near-zero threshold\n"
456 "(smoke test).";
457 return AST_TEST_NOT_RUN;
458 case TEST_EXECUTE:
459 break;
460 }
461
463 "Starting codec roundtrip tests (%d codecs, %d seconds of audio)\n",
465
466 for (i = 0; i < (int)ARRAY_LEN(codec_table); i++) {
467 struct ast_format *slin_fmt;
468 struct ast_format *target_fmt;
469 int16_t *orig_buf;
470 enum ast_test_result_state res;
471 int rate = codec_table[i].sample_rate > 0
472 ? codec_table[i].sample_rate : 8000;
473 int total_samples = rate * TEST_DURATION_SECS;
474
475 /* Select the slin format for this codec's native rate */
476 switch (rate) {
477 case 16000:
478 slin_fmt = ast_format_slin16;
479 break;
480 case 32000:
481 slin_fmt = ast_format_slin32;
482 break;
483 case 48000:
484 slin_fmt = ast_format_slin48;
485 break;
486 default:
487 slin_fmt = ast_format_slin;
488 break;
489 }
490
491 target_fmt = ast_format_cache_get(codec_table[i].format_name);
492 if (!target_fmt
493 || ast_translate_path_steps(target_fmt, slin_fmt) == -1) {
495 " %s: no translation path available, skipping\n",
496 codec_table[i].format_name);
497 ao2_cleanup(target_fmt);
498 continue;
499 }
500
501 orig_buf = ast_malloc(total_samples * sizeof(int16_t));
502 if (!orig_buf) {
503 ao2_ref(target_fmt, -1);
504 overall = AST_TEST_FAIL;
505 break;
506 }
507
508 generate_speech_signal(orig_buf, total_samples, rate);
509
510 res = check_codec(
511 test, slin_fmt, target_fmt, orig_buf,
512 total_samples, rate,
513 codec_table[i].is_lossy,
514 codec_table[i].min_snr_db != 0.0
515 ? codec_table[i].min_snr_db : MIN_SNR_LOSSY_DB);
516
517 ast_free(orig_buf);
518 ao2_ref(target_fmt, -1);
519
520 tested++;
521 if (res == AST_TEST_FAIL) {
522 failed++;
523 overall = AST_TEST_FAIL;
524 }
525 }
526
528 "\nCodec roundtrip summary: %d tested, %d passed, %d failed\n",
529 tested, tested - failed, failed);
530
531 if (tested == 0) {
533 "WARNING: No codecs were available to test. "
534 "Ensure codec modules are loaded.\n");
535 }
536
537 return overall;
538}
539
540static int unload_module(void)
541{
542 AST_TEST_UNREGISTER(codec_translate_roundtrip);
543 return 0;
544}
545
546static int load_module(void)
547{
548 AST_TEST_REGISTER(codec_translate_roundtrip);
550}
551
552AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "Codec Translation Roundtrip Tests");
static int copy(char *infile, char *outfile)
Utility function to copy a file.
Asterisk main include file. File version handling, generic pbx functions.
#define ast_free(a)
Definition astmm.h:180
#define ast_calloc(num, len)
A wrapper for calloc()
Definition astmm.h:202
#define ast_malloc(len)
A wrapper for malloc()
Definition astmm.h:191
#define ao2_cleanup(obj)
Definition astobj2.h:1934
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition astobj2.h:459
static PGresult * result
Definition cel_pgsql.c:84
Codec API.
char buf[BUFSIZE]
Definition eagi_proxy.c:66
#define abs(x)
Definition f2c.h:195
Media Format API.
const char * ast_format_get_name(const struct ast_format *format)
Get the name associated with a format.
Definition format.c:334
Media Format Cache API.
struct ast_format * ast_format_slin32
Built-in cached signed linear 32kHz format.
struct ast_format * ast_format_slin16
Built-in cached signed linear 16kHz format.
struct ast_format * ast_format_slin48
Built-in cached signed linear 48kHz format.
#define ast_format_cache_get(name)
Retrieve a named format from the cache.
struct ast_format * ast_format_slin
Built-in cached signed linear 8kHz format.
Format Capabilities API.
Asterisk internal frame definitions.
Support for logging to various files, console and syslog Configuration in file logger....
#define AST_LIST_NEXT(elm, field)
Returns the next entry in the list after the given entry.
Asterisk module definitions.
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition module.h:46
#define AST_MODULE_INFO_STANDARD_EXTENDED(keystr, desc)
Definition module.h:589
@ AST_MODULE_LOAD_SUCCESS
Definition module.h:70
static void cleanup(void)
Clean up any old apps that we don't need any more.
Definition res_stasis.c:327
#define NULL
Definition resample.c:96
#define M_PI
Definition resample.c:83
Definition of a media format.
Definition format.c:43
struct ast_format * format
Data structure associated with a single frame of data.
struct ast_frame_subclass subclass
enum ast_frame_type frametype
union ast_frame::@235 data
Default structure for translators, with the basic fields and buffers, all allocated as part of the sa...
Definition translate.h:213
Codec roundtrip entry: table of codecs to test.
double min_snr_db
int is_lossy
int sample_rate
const char * format_name
Test Framework API.
@ TEST_INIT
Definition test.h:200
@ TEST_EXECUTE
Definition test.h:201
#define AST_TEST_REGISTER(cb)
Definition test.h:127
#define ast_test_status_update(a, b, c...)
Definition test.h:129
#define AST_TEST_UNREGISTER(cb)
Definition test.h:128
#define AST_TEST_DEFINE(hdr)
Definition test.h:126
ast_test_result_state
Definition test.h:193
@ AST_TEST_PASS
Definition test.h:195
@ AST_TEST_FAIL
Definition test.h:196
@ AST_TEST_NOT_RUN
Definition test.h:194
static void generate_speech_signal(int16_t *buf, int samples, int sample_rate)
Generate a synthetic speech-like test signal in linear sample.
static double compute_snr_aligned(const int16_t *orig, const int16_t *decoded, int samples, int max_delay, int *delay_out)
Compute SNR after aligning the decoded signal via cross-correlation.
#define MIN_DECODED_RATIO
#define MIN_SNR_LOSSY_DB
#define CHUNK_MS
static double compute_snr(const int16_t *orig, const int16_t *roundtrip, int samples)
Compute Signal-to-Noise Ratio between original and roundtripped signal.
#define MAX_SAMPLE_ERR_LOSSLESS
static enum ast_test_result_state check_codec(struct ast_test *test, struct ast_format *slin_fmt, struct ast_format *target_fmt, const int16_t *orig_buf, int total_samples, int sample_rate, int is_lossy, double min_snr_db)
Attempt a roundtrip encode/decode for one codec format.
#define TEST_DURATION_SECS
static int load_module(void)
static const struct codec_test_entry codec_table[]
static int unload_module(void)
static int compute_max_error(const int16_t *orig, const int16_t *roundtrip, int samples)
Compute maximum absolute per-sample error.
static struct test_val d
Support for translation of data formats. translate.c.
struct ast_frame * ast_translate(struct ast_trans_pvt *tr, struct ast_frame *f, int consume)
translates one or more frames Apply an input frame into the translator and receive zero or one output...
Definition translate.c:623
void ast_translator_free_path(struct ast_trans_pvt *tr)
Frees a translator path Frees the given translator path structure.
Definition translate.c:533
struct ast_trans_pvt * ast_translator_build_path(struct ast_format *dest, struct ast_format *source)
Builds a translator path Build a path (possibly NULL) from source to dest.
Definition translate.c:543
unsigned int ast_translate_path_steps(struct ast_format *dest, struct ast_format *src)
Returns the number of steps required to convert from 'src' to 'dest'.
Definition translate.c:1810
Utility functions.
#define ARRAY_LEN(a)
Definition utils.h:706