int super_tone_rx(super_tone_rx_state_t *s, const int16_t amp[], int samples) { int i; int x; int sample; #if defined(SPANDSP_USE_FIXED_POINT) int16_t xamp; #else float xamp; #endif x = 0; for (sample = 0; sample < samples; sample += x) { for (i = 0; i < s->desc->monitored_frequencies; i++) x = goertzel_update(&s->state[i], amp + sample, samples - sample); for (i = 0; i < x; i++) { xamp = goertzel_preadjust_amp(amp[sample + i]); #if defined(SPANDSP_USE_FIXED_POINT) s->energy += ((int32_t) xamp*xamp); #else s->energy += xamp*xamp; #endif } if (s->state[0].current_sample >= BINS) { /* We have finished a Goertzel block. */ super_tone_chunk(s); s->energy = 0; } } return samples; }
SPAN_DECLARE(int) ademco_contactid_sender_rx(ademco_contactid_sender_state_t *s, const int16_t amp[], int samples) { #if defined(SPANDSP_USE_FIXED_POINT) int32_t energy_1400; int32_t energy_2300; int16_t xamp; #else float energy_1400; float energy_2300; float xamp; #endif int sample; int limit; int hit; int j; for (sample = 0; sample < samples; sample = limit) { if ((samples - sample) >= (GOERTZEL_SAMPLES_PER_BLOCK - s->current_sample)) limit = sample + (GOERTZEL_SAMPLES_PER_BLOCK - s->current_sample); else limit = samples; for (j = sample; j < limit; j++) { xamp = amp[j]; xamp = goertzel_preadjust_amp(xamp); #if defined(SPANDSP_USE_FIXED_POINT) s->energy += ((int32_t) xamp*xamp); #else s->energy += xamp*xamp; #endif goertzel_samplex(&s->tone_1400, xamp); goertzel_samplex(&s->tone_2300, xamp); } s->current_sample += (limit - sample); if (s->current_sample < GOERTZEL_SAMPLES_PER_BLOCK) continue; energy_1400 = goertzel_result(&s->tone_1400); energy_2300 = goertzel_result(&s->tone_2300); hit = 0; if (energy_1400 > DETECTION_THRESHOLD || energy_2300 > DETECTION_THRESHOLD) { if (energy_1400 > energy_2300) { if (energy_1400 > TONE_TO_TOTAL_ENERGY*s->energy) hit = 1; } else { if (energy_2300 > TONE_TO_TOTAL_ENERGY*s->energy) hit = 2; } } if (hit != s->in_tone && hit == s->last_hit) { /* We have two successive indications that something has changed to a specific new state. */ switch (s->tone_state) { case 0: if (hit == 1) { span_log(&s->logging, SPAN_LOG_FLOW, "Receiving initial 1400Hz\n"); s->in_tone = hit; s->tone_state = 1; s->duration = 0; } break; case 1: /* We are looking for a burst of 1400Hz which is 100ms +- 5% long */ if (hit == 0) { if (s->duration < ms_to_samples(70) || s->duration > ms_to_samples(130)) { span_log(&s->logging, SPAN_LOG_FLOW, "Bad initial 1400Hz tone duration\n"); s->tone_state = 0; } else { span_log(&s->logging, SPAN_LOG_FLOW, "Received 1400Hz tone\n"); s->tone_state = 2; } s->in_tone = hit; s->duration = 0; } break; case 2: /* We are looking for 100ms +-5% of silence after the 1400Hz tone */ if (s->duration < ms_to_samples(70) || s->duration > ms_to_samples(130)) { span_log(&s->logging, SPAN_LOG_FLOW, "Bad silence length\n"); s->tone_state = 0; s->in_tone = hit; } else if (hit == 2) { span_log(&s->logging, SPAN_LOG_FLOW, "Received silence\n"); s->tone_state = 3; s->in_tone = hit; } else { s->tone_state = 0; s->in_tone = 0; } s->duration = 0; break; case 3: /* We are looking for a burst of 2300Hz which is 100ms +- 5% long */ if (hit == 0) { if (s->duration < ms_to_samples(70) || s->duration > ms_to_samples(130)) { span_log(&s->logging, SPAN_LOG_FLOW, "Bad initial 2300Hz tone duration\n"); s->tone_state = 0; } else { span_log(&s->logging, SPAN_LOG_FLOW, "Received 2300Hz\n"); if (s->callback) s->callback(s->callback_user_data, -1, 0, 0); s->tone_state = 4; /* Release the transmit side, and it will time the 250ms post tone delay */ s->clear_to_send = true; s->tries = 0; if (s->tx_digits_len) s->timer = ms_to_samples(3000); } s->in_tone = hit; s->duration = 0; } break; case 4: if (hit == 1) { span_log(&s->logging, SPAN_LOG_FLOW, "Receiving kissoff\n"); s->tone_state = 5; s->in_tone = hit; s->duration = 0; } break; case 5: if (hit == 0) { s->busy = false; if (s->duration < ms_to_samples(400) || s->duration > ms_to_samples(1500)) { span_log(&s->logging, SPAN_LOG_FLOW, "Bad kissoff duration %d\n", s->duration); if (++s->tries < 4) { dtmf_tx_put(&s->dtmf, s->tx_digits, s->tx_digits_len); s->timer = ms_to_samples(3000); s->tone_state = 4; } else { s->timer = 0; if (s->callback) s->callback(s->callback_user_data, false, 0, 0); } } else { span_log(&s->logging, SPAN_LOG_FLOW, "Received good kissoff\n"); s->clear_to_send = true; s->tx_digits_len = 0; if (s->callback) s->callback(s->callback_user_data, true, 0, 0); s->tone_state = 4; s->clear_to_send = true; s->tries = 0; if (s->tx_digits_len) s->timer = ms_to_samples(3000); } s->in_tone = hit; s->duration = 0; } break; } } s->last_hit = hit; s->duration += GOERTZEL_SAMPLES_PER_BLOCK; if (s->timer > 0) { s->timer -= GOERTZEL_SAMPLES_PER_BLOCK; if (s->timer <= 0) { span_log(&s->logging, SPAN_LOG_FLOW, "Timer expired\n"); if (s->tone_state == 4 && s->tx_digits_len) { if (++s->tries < 4) { dtmf_tx_put(&s->dtmf, s->tx_digits, s->tx_digits_len); s->timer = ms_to_samples(3000); } else { s->timer = 0; if (s->callback) s->callback(s->callback_user_data, false, 0, 0); } } } } s->energy = 0; s->current_sample = 0; } return 0; }
SPAN_DECLARE(int) r2_mf_rx(r2_mf_rx_state_t *s, const int16_t amp[], int samples) { #if defined(SPANDSP_USE_FIXED_POINT) int32_t energy[6]; int16_t xamp; #else float energy[6]; float xamp; #endif int i; int j; int sample; int best; int second_best; int hit; int hit_digit; int limit; hit = 0; hit_digit = 0; for (sample = 0; sample < samples; sample = limit) { if ((samples - sample) >= (R2_MF_SAMPLES_PER_BLOCK - s->current_sample)) limit = sample + (R2_MF_SAMPLES_PER_BLOCK - s->current_sample); else limit = samples; for (j = sample; j < limit; j++) { xamp = goertzel_preadjust_amp(amp[j]); goertzel_samplex(&s->out[0], xamp); goertzel_samplex(&s->out[1], xamp); goertzel_samplex(&s->out[2], xamp); goertzel_samplex(&s->out[3], xamp); goertzel_samplex(&s->out[4], xamp); goertzel_samplex(&s->out[5], xamp); } s->current_sample += (limit - sample); if (s->current_sample < R2_MF_SAMPLES_PER_BLOCK) continue; /* We are at the end of an MF detection block */ /* Find the two highest energies */ energy[0] = goertzel_result(&s->out[0]); energy[1] = goertzel_result(&s->out[1]); if (energy[0] > energy[1]) { best = 0; second_best = 1; } else { best = 1; second_best = 0; } for (i = 2; i < 6; i++) { energy[i] = goertzel_result(&s->out[i]); if (energy[i] >= energy[best]) { second_best = best; best = i; } else if (energy[i] >= energy[second_best]) { second_best = i; } } /* Basic signal level and twist tests */ hit = FALSE; if (energy[best] >= R2_MF_THRESHOLD && energy[second_best] >= R2_MF_THRESHOLD && energy[best] < energy[second_best]*R2_MF_TWIST && energy[best]*R2_MF_TWIST > energy[second_best]) { /* Relative peak test */ hit = TRUE; for (i = 0; i < 6; i++) { if (i != best && i != second_best) { if (energy[i]*R2_MF_RELATIVE_PEAK >= energy[second_best]) { /* The best two are not clearly the best */ hit = FALSE; break; } } } } if (hit) { /* Get the values into ascending order */ if (second_best < best) { i = best; best = second_best; second_best = i; } best = best*5 + second_best - 1; hit_digit = r2_mf_positions[best]; } else { hit_digit = 0; } if (s->current_digit != hit_digit && s->callback) { i = (hit_digit) ? -10 : -99; s->callback(s->callback_data, hit_digit, i, 0); } s->current_digit = hit_digit; s->current_sample = 0; } return 0; }
SPAN_DECLARE(int) bell_mf_rx(bell_mf_rx_state_t *s, const int16_t amp[], int samples) { #if defined(SPANDSP_USE_FIXED_POINT) int32_t energy[6]; int16_t xamp; #else float energy[6]; float xamp; #endif int i; int j; int sample; int best; int second_best; int limit; uint8_t hit; hit = 0; for (sample = 0; sample < samples; sample = limit) { if ((samples - sample) >= (BELL_MF_SAMPLES_PER_BLOCK - s->current_sample)) limit = sample + (BELL_MF_SAMPLES_PER_BLOCK - s->current_sample); else limit = samples; for (j = sample; j < limit; j++) { xamp = goertzel_preadjust_amp(amp[j]); goertzel_samplex(&s->out[0], xamp); goertzel_samplex(&s->out[1], xamp); goertzel_samplex(&s->out[2], xamp); goertzel_samplex(&s->out[3], xamp); goertzel_samplex(&s->out[4], xamp); goertzel_samplex(&s->out[5], xamp); } s->current_sample += (limit - sample); if (s->current_sample < BELL_MF_SAMPLES_PER_BLOCK) continue; /* We are at the end of an MF detection block */ /* Find the two highest energies. The spec says to look for two tones and two tones only. Taking this literally -ie only two tones pass the minimum threshold - doesn't work well. The sinc function mess, due to rectangular windowing ensure that! Find the two highest energies and ensure they are considerably stronger than any of the others. */ energy[0] = goertzel_result(&s->out[0]); energy[1] = goertzel_result(&s->out[1]); if (energy[0] > energy[1]) { best = 0; second_best = 1; } else { best = 1; second_best = 0; } for (i = 2; i < 6; i++) { energy[i] = goertzel_result(&s->out[i]); if (energy[i] >= energy[best]) { second_best = best; best = i; } else if (energy[i] >= energy[second_best]) { second_best = i; } } /* Basic signal level and twist tests */ hit = 0; if (energy[best] >= BELL_MF_THRESHOLD && energy[second_best] >= BELL_MF_THRESHOLD && energy[best] < energy[second_best]*BELL_MF_TWIST && energy[best]*BELL_MF_TWIST > energy[second_best]) { /* Relative peak test */ hit = 'X'; for (i = 0; i < 6; i++) { if (i != best && i != second_best) { if (energy[i]*BELL_MF_RELATIVE_PEAK >= energy[second_best]) { /* The best two are not clearly the best */ hit = 0; break; } } } } if (hit) { /* Get the values into ascending order */ if (second_best < best) { i = best; best = second_best; second_best = i; } best = best*5 + second_best - 1; hit = bell_mf_positions[best]; /* Look for two successive similar results */ /* The logic in the next test is: For KP we need 4 successive identical clean detects, with two blocks of something different preceeding it. For anything else we need two successive identical clean detects, with two blocks of something different preceeding it. */ if (hit == s->hits[4] && hit == s->hits[3] && ((hit != '*' && hit != s->hits[2] && hit != s->hits[1]) || (hit == '*' && hit == s->hits[2] && hit != s->hits[1] && hit != s->hits[0]))) { if (s->current_digits < MAX_BELL_MF_DIGITS) { s->digits[s->current_digits++] = (char) hit; s->digits[s->current_digits] = '\0'; if (s->digits_callback) { s->digits_callback(s->digits_callback_data, s->digits, s->current_digits); s->current_digits = 0; } } else { s->lost_digits++; } } } s->hits[0] = s->hits[1]; s->hits[1] = s->hits[2]; s->hits[2] = s->hits[3]; s->hits[3] = s->hits[4]; s->hits[4] = hit; s->current_sample = 0; } if (s->current_digits && s->digits_callback) { s->digits_callback(s->digits_callback_data, s->digits, s->current_digits); s->digits[0] = '\0'; s->current_digits = 0; } return 0; }
SPAN_DECLARE(int) dtmf_rx(dtmf_rx_state_t *s, const int16_t amp[], int samples) { #if defined(SPANDSP_USE_FIXED_POINT) int32_t row_energy[4]; int32_t col_energy[4]; int16_t xamp; float famp; #else float row_energy[4]; float col_energy[4]; float xamp; float famp; #endif float v1; int i; int j; int sample; int best_row; int best_col; int limit; uint8_t hit; hit = 0; for (sample = 0; sample < samples; sample = limit) { /* The block length is optimised to meet the DTMF specs. */ if ((samples - sample) >= (DTMF_SAMPLES_PER_BLOCK - s->current_sample)) limit = sample + (DTMF_SAMPLES_PER_BLOCK - s->current_sample); else limit = samples; /* The following unrolled loop takes only 35% (rough estimate) of the time of a rolled loop on the machine on which it was developed */ for (j = sample; j < limit; j++) { xamp = amp[j]; if (s->filter_dialtone) { famp = xamp; /* Sharp notches applied at 350Hz and 440Hz - the two common dialtone frequencies. These are rather high Q, to achieve the required narrowness, without using lots of sections. */ v1 = 0.98356f*famp + 1.8954426f*s->z350[0] - 0.9691396f*s->z350[1]; famp = v1 - 1.9251480f*s->z350[0] + s->z350[1]; s->z350[1] = s->z350[0]; s->z350[0] = v1; v1 = 0.98456f*famp + 1.8529543f*s->z440[0] - 0.9691396f*s->z440[1]; famp = v1 - 1.8819938f*s->z440[0] + s->z440[1]; s->z440[1] = s->z440[0]; s->z440[0] = v1; xamp = famp; } xamp = goertzel_preadjust_amp(xamp); #if defined(SPANDSP_USE_FIXED_POINT) s->energy += ((int32_t) xamp*xamp); #else s->energy += xamp*xamp; #endif goertzel_samplex(&s->row_out[0], xamp); goertzel_samplex(&s->col_out[0], xamp); goertzel_samplex(&s->row_out[1], xamp); goertzel_samplex(&s->col_out[1], xamp); goertzel_samplex(&s->row_out[2], xamp); goertzel_samplex(&s->col_out[2], xamp); goertzel_samplex(&s->row_out[3], xamp); goertzel_samplex(&s->col_out[3], xamp); } if (s->duration < INT_MAX - (limit - sample)) s->duration += (limit - sample); s->current_sample += (limit - sample); if (s->current_sample < DTMF_SAMPLES_PER_BLOCK) continue; /* We are at the end of a DTMF detection block */ /* Find the peak row and the peak column */ row_energy[0] = goertzel_result(&s->row_out[0]); best_row = 0; col_energy[0] = goertzel_result(&s->col_out[0]); best_col = 0; for (i = 1; i < 4; i++) { row_energy[i] = goertzel_result(&s->row_out[i]); if (row_energy[i] > row_energy[best_row]) best_row = i; col_energy[i] = goertzel_result(&s->col_out[i]); if (col_energy[i] > col_energy[best_col]) best_col = i; } hit = 0; /* Basic signal level test and the twist test */ if (row_energy[best_row] >= s->threshold && col_energy[best_col] >= s->threshold) { if (col_energy[best_col] < row_energy[best_row]*s->reverse_twist && col_energy[best_col]*s->normal_twist > row_energy[best_row]) { /* Relative peak test ... */ for (i = 0; i < 4; i++) { if ((i != best_col && col_energy[i]*DTMF_RELATIVE_PEAK_COL > col_energy[best_col]) || (i != best_row && row_energy[i]*DTMF_RELATIVE_PEAK_ROW > row_energy[best_row])) { break; } } /* ... and fraction of total energy test */ if (i >= 4 && (row_energy[best_row] + col_energy[best_col]) > DTMF_TO_TOTAL_ENERGY*s->energy) { /* Got a hit */ hit = dtmf_positions[(best_row << 2) + best_col]; } } if (span_log_test(&s->logging, SPAN_LOG_FLOW)) { /* Log information about the quality of the signal, to aid analysis of detection problems */ /* Logging at this point filters the total no-hoper frames out of the log, and leaves anything which might feasibly be a DTMF digit. The log will then contain a list of the total, row and coloumn power levels for detailed analysis of detection problems. */ span_log(&s->logging, SPAN_LOG_FLOW, "Potentially '%c' - total %.2fdB, row %.2fdB, col %.2fdB, duration %d - %s\n", dtmf_positions[(best_row << 2) + best_col], log10f(s->energy)*10.0f - DTMF_POWER_OFFSET + DBM0_MAX_POWER, log10f(row_energy[best_row]/DTMF_TO_TOTAL_ENERGY)*10.0f - DTMF_POWER_OFFSET + DBM0_MAX_POWER, log10f(col_energy[best_col]/DTMF_TO_TOTAL_ENERGY)*10.0f - DTMF_POWER_OFFSET + DBM0_MAX_POWER, s->duration, (hit) ? "hit" : "miss"); } } /* The logic in the next test should ensure the following for different successive hit patterns: -----ABB = start of digit B. ----B-BB = start of digit B ----A-BB = start of digit B BBBBBABB = still in digit B. BBBBBB-- = end of digit B BBBBBBC- = end of digit B BBBBACBB = B ends, then B starts again. BBBBBBCC = B ends, then C starts. BBBBBCDD = B ends, then D starts. This can work with: - Back to back differing digits. Back-to-back digits should not happen. The spec. says there should be a gap between digits. However, many real phones do not impose a gap, and rolling across the keypad can produce little or no gap. - It tolerates nasty phones that give a very wobbly start to a digit. - VoIP can give sample slips. The phase jumps that produces will cause the block it is in to give no detection. This logic will ride over a single missed block, and not falsely declare a second digit. If the hiccup happens in the wrong place on a minimum length digit, however we would still fail to detect that digit. Could anything be done to deal with that? Packet loss is clearly a no-go zone. Note this is only relevant to VoIP using A-law, u-law or similar. Low bit rate codecs scramble DTMF too much for it to be recognised, and often slip in units larger than a sample. */ if (hit != s->in_digit && s->last_hit != s->in_digit) { /* We have two successive indications that something has changed. */ /* To declare digit on, the hits must agree. Otherwise we declare tone off. */ hit = (hit && hit == s->last_hit) ? hit : 0; if (s->realtime_callback) { /* Avoid reporting multiple no digit conditions on flaky hits */ if (s->in_digit || hit) { i = (s->in_digit && !hit) ? -99 : lfastrintf(log10f(s->energy)*10.0f - DTMF_POWER_OFFSET + DBM0_MAX_POWER); s->realtime_callback(s->realtime_callback_data, hit, i, s->duration); s->duration = 0; } } else { if (hit) { if (s->current_digits < MAX_DTMF_DIGITS) { s->digits[s->current_digits++] = (char) hit; s->digits[s->current_digits] = '\0'; if (s->digits_callback) { s->digits_callback(s->digits_callback_data, s->digits, s->current_digits); s->current_digits = 0; } } else { s->lost_digits++; } } } s->in_digit = hit; } s->last_hit = hit; #if defined(SPANDSP_USE_FIXED_POINT) s->energy = 0; #else s->energy = 0.0f; #endif s->current_sample = 0; } if (s->current_digits && s->digits_callback) { s->digits_callback(s->digits_callback_data, s->digits, s->current_digits); s->digits[0] = '\0'; s->current_digits = 0; } return 0; }