Commit 4b9ef3f5e2 for asterisk.org
commit 4b9ef3f5e21e50b0a57914f504b212ede34b9963
Author: Sebastian Jennen <sebastian.jennen@gmx.de>
Date: Thu Apr 2 10:33:44 2026 +0200
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
diff --git a/main/translate.c b/main/translate.c
index 3253bd4537..9d893de251 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 */