From dddee1362c66e6ddee4e33b08d98bf23967a201c Mon Sep 17 00:00:00 2001 From: Sebastian Jennen Date: Thu, 2 Apr 2026 10:33:44 +0200 Subject: [PATCH] translate.c: implement different sample_types for translation computation. The default (codec) still uses the codec provided samples. Additionally different sample_types can be used with eg: `translate sampletype speech` and then running `core show translation comp 10` to measure performance of different audio scenarios. Resolves: #1807 --- main/translate.c | 300 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 287 insertions(+), 13 deletions(-) diff --git a/main/translate.c b/main/translate.c index de092e0c99..f1f0fb0cc7 100644 --- a/main/translate.c +++ b/main/translate.c @@ -44,11 +44,68 @@ #include "asterisk/format.h" #include "asterisk/linkedlists.h" -/*! \todo - * TODO: sample frames for each supported input format. - * We build this on the fly, by taking an SLIN frame and using - * the existing converter to play with it. +/* + * Sample type selection for codec matrix cost computation. */ +enum sample_type { + SAMPLE_TYPE_CODEC = 0, /*!< Default: take samples defined by codec */ + SAMPLE_TYPE_SINE, /*!< Default: 1kHz sine wave */ + SAMPLE_TYPE_SILENCE, /*!< All-zero frames */ + SAMPLE_TYPE_NOISE, /*!< White noise via LCG PRNG */ + SAMPLE_TYPE_SPEECH, /*!< Synthetic speech-like signal */ +}; + +static enum sample_type current_sample_type = SAMPLE_TYPE_CODEC; + +/*! Simple LCG state for reproducible pseudo-random noise */ +static uint32_t lcg_state = 0xdeadbeef; +static uint32_t lcg_rand(void) +{ + lcg_state = lcg_state * 1664525u + 1013904223u; + return lcg_state; +} + +/*! + * \brief Fill a buffer with samples of the selected type. + * \param buf Output sample buffer (int16_t) + * \param samples Number of samples to generate + * \param offset Running sample offset (for continuous sample types) + */ +static void generate_samples(int16_t *buf, int samples, int offset) +{ + int i; + switch (current_sample_type) { + case SAMPLE_TYPE_SILENCE: + memset(buf, 0, samples * sizeof(int16_t)); + break; + case SAMPLE_TYPE_NOISE: + for (i = 0; i < samples; i++) { + buf[i] = (int16_t)(lcg_rand() & 0xFFFF); + } + break; + case SAMPLE_TYPE_SPEECH: + /* Synthetic speech approximation: mix of two sine waves + * (fundamental ~200Hz + harmonic ~800Hz) with slow AM envelope, + * loosely inspired by ITU-T P.50 artificial voice. */ + for (i = 0; i < samples; i++) { + double t = (double)(offset + i) / 8000.0; + double sig = 0.6 * sin(2.0 * M_PI * 200.0 * t) + + 0.4 * sin(2.0 * M_PI * 800.0 * t); + /* Slow AM at 4Hz to simulate syllable rhythm */ + sig *= 0.5 * (1.0 + sin(2.0 * M_PI * 4.0 * t)); + buf[i] = (int16_t)(sig * 16000.0); + } + break; + case SAMPLE_TYPE_SINE: + default: + /* pure 1kHz sine wave */ + for (i = 0; i < samples; i++) { + buf[i] = (int16_t)(sin(2.0 * M_PI * 1000.0 * + (double)(offset + i) / 8000.0) * 16000.0); + } + break; + } +} /*! max sample recalc */ #define MAX_RECALC 1000 @@ -679,6 +736,70 @@ struct ast_frame *ast_translate(struct ast_trans_pvt *path, struct ast_frame *f, return out; } +/*! Maximum number of pre-encoded frames cached for non-slin benchmark sources */ +#define PRE_ENCODE_POOL_MAX 200 + +/*! + * \internal + * \brief Build a translation path from slin (8 kHz) to the source codec of + * translator \p t, using the already-populated translation matrix. + * + * \note Must be called with the translators list locked. + * + * \retval NULL Source codec is already slin, or no path through the matrix exists. + */ +static struct ast_trans_pvt *build_slin_to_src_path(const struct ast_translator *t) +{ + struct ast_trans_pvt *head = NULL, *tail = NULL; + struct ast_codec *slin_codec; + int slin_index, dst_index; + + if (!strcmp(t->src_codec.name, "slin")) { + return NULL; /* source is already slin, no pre-encoding needed */ + } + + slin_codec = ast_codec_get("slin", AST_MEDIA_TYPE_AUDIO, 8000); + if (!slin_codec) { + return NULL; + } + + slin_index = codec2index(slin_codec); + ao2_ref(slin_codec, -1); + + if (slin_index < 0) { + return NULL; + } + + dst_index = t->src_fmt_index; + if (dst_index < 0 || slin_index == dst_index) { + return NULL; + } + + while (slin_index != dst_index) { + struct ast_trans_pvt *cur; + struct ast_translator *step = matrix_get(slin_index, dst_index)->step; + if (!step) { + ast_translator_free_path(head); + return NULL; + } + cur = newpvt(step, NULL); + if (!cur) { + ast_translator_free_path(head); + return NULL; + } + if (!head) { + head = cur; + } else { + tail->next = cur; + } + tail = cur; + cur->nextin = cur->nextout = ast_tv(0, 0); + slin_index = cur->t->dst_fmt_index; + } + + return head; +} + /*! * \internal * \brief Compute the computational cost of a single translation step. @@ -696,6 +817,10 @@ static void generate_computational_cost(struct ast_translator *t, int seconds) struct rusage end; int cost; int out_rate = t->dst_codec.sample_rate; + struct ast_frame **pre_pool = NULL; + int pre_pool_count = 0; + int pre_pool_idx = 0; + int use_pre_pool = 0; if (!seconds) { seconds = 1; @@ -715,19 +840,100 @@ static void generate_computational_cost(struct ast_translator *t, int seconds) return; } + /* + * For non-CODEC sample types, pre-generate frames before starting the + * timer so sample-generation overhead is excluded from the measurement. + * + * For slin sources: isolate a fresh copy of each generated frame directly. + * For non-slin sources: route the generated slin frames through a + * slin->src_codec encoding path first. If no such path exists in the + * current matrix, use_pre_pool stays 0 and we fall back to t->sample(). + */ + if (current_sample_type != SAMPLE_TYPE_CODEC) { + int16_t slin_buf[80]; + struct ast_frame slin_f; + struct ast_trans_pvt *pre_path = NULL; + int attempt; + + if (strcmp(t->src_codec.name, "slin")) { + pre_path = build_slin_to_src_path(t); + } + + if (!strcmp(t->src_codec.name, "slin") || pre_path) { + pre_pool = ast_malloc(PRE_ENCODE_POOL_MAX * sizeof(*pre_pool)); + if (pre_pool) { + memset(&slin_f, 0, sizeof(slin_f)); + slin_f.frametype = AST_FRAME_VOICE; + slin_f.datalen = sizeof(slin_buf); + slin_f.samples = ARRAY_LEN(slin_buf); + slin_f.data.ptr = slin_buf; + slin_f.src = __PRETTY_FUNCTION__; + slin_f.subclass.format = ast_format_slin; + + for (attempt = 0; pre_pool_count < PRE_ENCODE_POOL_MAX; attempt++) { + struct ast_frame *out; + + if (attempt >= PRE_ENCODE_POOL_MAX * 10) { + break; /* encoder not producing output; give up */ + } + + generate_samples(slin_buf, ARRAY_LEN(slin_buf), + attempt * (int)ARRAY_LEN(slin_buf)); + + if (pre_path) { + /* non-slin: encode through the pre_path chain */ + struct ast_trans_pvt *pp; + out = &slin_f; + for (pp = pre_path; pp; pp = pp->next) { + struct ast_frame *next_out; + framein(pp, out); + if (out != &slin_f) { + ast_frfree(out); + } + next_out = pp->t->frameout(pp); + out = next_out; + if (!out) { + break; + } + } + } else { + /* slin: isolate a standalone copy of the frame */ + out = ast_frisolate(&slin_f); + } + + if (out) { + pre_pool[pre_pool_count++] = out; + } + } + use_pre_pool = (pre_pool_count > 0); + } + ast_translator_free_path(pre_path); + } + } + getrusage(RUSAGE_SELF, &start); /* Call the encoder until we've processed the required number of samples */ while (num_samples < seconds * out_rate) { - struct ast_frame *f = t->sample(); - if (!f) { - ast_log(LOG_WARNING, "Translator '%s' failed to produce a sample frame.\n", t->name); - destroy(pvt); - t->comp_cost = 999999; - return; + struct ast_frame *f; + + if (use_pre_pool) { + /* cycle through the pre-generated frame pool */ + framein(pvt, pre_pool[pre_pool_idx % pre_pool_count]); + pre_pool_idx++; + } else { + /* default: use the codec's own sample generator */ + f = t->sample(); + if (!f) { + ast_log(LOG_WARNING, "Translator '%s' failed to produce a sample frame.\n", t->name); + destroy(pvt); + t->comp_cost = 999999; + goto cleanup; + } + framein(pvt, f); + ast_frfree(f); } - framein(pvt, f); - ast_frfree(f); + while ((f = t->frameout(pvt))) { num_samples += f->samples; ast_frfree(f); @@ -746,6 +952,15 @@ static void generate_computational_cost(struct ast_translator *t, int seconds) if (!t->comp_cost) { t->comp_cost = 1; } + +cleanup: + if (pre_pool) { + int i; + for (i = 0; i < pre_pool_count; i++) { + ast_frfree(pre_pool[i]); + } + ast_free(pre_pool); + } } /*! @@ -1229,8 +1444,67 @@ static char *handle_cli_core_show_translation(struct ast_cli_entry *e, int cmd, return handle_show_translation_table(a); } +static char *handle_cli_translate_sampletype(struct ast_cli_entry *e, + int cmd, struct ast_cli_args *a) +{ + static const char * const types[] = { "codec", "sine", "silence", "noise", "speech", NULL }; + + switch (cmd) { + case CLI_INIT: + e->command = "translate sampletype"; + e->usage = + "Usage: translate sampletype [codec|sine|silence|noise|speech]\n" + " Get or set the sample type used for codec matrix cost\n" + " computation. Default is 'codec'.\n" + " codec - samples defined inside codec\n" + " sine - 1kHz sine wave\n" + " silence - All-zero frames\n" + " noise - White noise\n" + " speech - Synthetic speech-like signal (P.50 inspired)\n"; + return NULL; + case CLI_GENERATE: + return ast_cli_complete(a->word, types, a->n); + } + + if (a->argc == 2) { + /* Query current setting */ + const char *names[] = { "codec", "sine", "silence", "noise", "speech" }; + ast_cli(a->fd, "Current translate sample type: %s\n", + names[current_sample_type]); + return CLI_SUCCESS; + } + + if (a->argc != 3) + return CLI_SHOWUSAGE; + + if (!strcasecmp(a->argv[2], "silence")) { + current_sample_type = SAMPLE_TYPE_SILENCE; + } else if (!strcasecmp(a->argv[2], "noise")) { + current_sample_type = SAMPLE_TYPE_NOISE; + } else if (!strcasecmp(a->argv[2], "speech")) { + current_sample_type = SAMPLE_TYPE_SPEECH; + } else if (!strcasecmp(a->argv[2], "sine")) { + current_sample_type = SAMPLE_TYPE_SINE; + } else if (!strcasecmp(a->argv[2], "codec")) { + current_sample_type = SAMPLE_TYPE_CODEC; + } else { + ast_cli(a->fd, "Unknown sample type '%s'. Use: codec, sine, silence, noise, speech\n", + a->argv[2]); + return CLI_SHOWUSAGE; + } + + /* Rebuild the matrix with the new sample type */ + ast_cli(a->fd, "Sample type set to '%s'. Rebuilding codec matrix...\n", a->argv[2]); + AST_RWLIST_WRLOCK(&translators); + matrix_rebuild(1); + AST_RWLIST_UNLOCK(&translators); + + return CLI_SUCCESS; +} + static struct ast_cli_entry cli_translate[] = { - AST_CLI_DEFINE(handle_cli_core_show_translation, "Display translation matrix") + AST_CLI_DEFINE(handle_cli_core_show_translation, "Display translation matrix"), + AST_CLI_DEFINE(handle_cli_translate_sampletype, "Get/set codec matrix sample type"), }; /*! \brief register codec translator */