Media-Bug Azure Transcription — Architecture Guide

This document describes the EFSwitch media-bug live transcription path that streams call audio to Azure Speech, receives final ASR text over ESL, and posts messages into Expertflow CCM for Agent Desk. It covers runtime components, call lifecycle, multi-tenant CCM routing, APISIX Bearer auth (Voice Connector parity), operational controls, and capacity notes.

1. Overview

1.1 Executive Summary

  • Transcription runs inside EFSwitch via mod_azure_transcribe (media-bug + Microsoft Speech SDK). Recognition is performed by Azure Speech in the cloud.

  • A separate systemd process, tmb-relay-azure (tmb_app_azure.py), is intentionally thin: ESL events, start/stop TX, map call fields, POST finals to CCM.

  • TX attaches only on CHANNEL_BRIDGE (not early media). Default layout is dual_mono: two mono Azure streams per bridged call (caller + agent).

  • The relay does not create the CX conversation; Voice Connector already reserved the agent / conversation. The relay only appends PLAIN messages to /ccm/message/receive.

  • Multi-tenant CCM URL: https://{FS domain_name}.{CCM_ROOT_DOMAIN}/ccm, with CCM_BASE_URL as fallback.

  • When EFCX APISIX auth is on: AUTH_ENABLED=true + API_USERNAME / API_PASS → POST {tenantFqdn}/unified-admin/keycloakLogin → Authorization: Bearer on CCM (shared helper relay/ccm_auth.py). Default is off (no regression on non-APISIX labs).

2. Architecture Diagram

Media-Bug Azure Transcription architecture diagram

2.1 Component flow

Step

From

To

What happens

1

Customer / Agent

EFSwitch

Bridged voice call

2

EFSwitch

mod_azure_transcribe + Speech SDK

Media-bug attaches; audio streamed

3

Module

Azure Speech

Cloud ASR

4

EFSwitch ESL

tmb-relay-azure

CHANNEL_BRIDGE, finals, hangup

5

Relay (optional)

unified-admin

keycloakLogin when AUTH_ENABLED=true

6

Relay

CCM

POST /message/receive (+ Bearer if auth on)

7

CCM

Agent Desk

Transcript visible on conversation

—

Voice Connector

CCM

Creates/attaches conversation (before TX)

2.2 Sequence Diagram

Bridged call sequence diagram with optional APISIX auth
  1. Voice Connector has already created the CX conversation.

  2. EFSwitch emits CHANNEL_BRIDGE (A+B UUIDs) to the relay over ESL.

  3. Relay applies tenant / call-type / allowlist gates.

  4. Relay starts uuid_azure_transcribe on caller and agent legs (dual_mono).

  5. Azure returns recognition events; EFSwitch forwards finals to the relay.

  6. If AUTH_ENABLED=true and no cached token: POST {fqdn}/unified-admin/keycloakLogin → cache data.token.

  7. Relay POSTs CCM /message/receive with Bearer when auth is on.

  8. On HTTP 401: invalidate cache, refresh token once, retry the CCM request once.

  9. On hangup / destroy: stop azure_transcribe once per call.

3. Component Responsibilities

Component

Location

Responsibility

mod_azure_transcribe

EFSwitch process; /var/lib/freeswitch/tmb/

Attach media-bugs; Speech SDK encode/stream to Azure

Microsoft Speech SDK libs

/var/lib/freeswitch/tmb/lib/ only (never /usr/lib)

Native ASR client libraries

Azure Speech

Microsoft cloud

Recognition

tmb-relay-azure

systemd; typically /root/transcription_media_bug

ESL, start/stop TX, CCM POST, auth

relay/ccm_auth.py (CxApiAuth)

Shared Azure + Deepgram + relay/

Optional Keycloak login, token cache, 401 refresh+retry

Voice Connector

Separate CX Voice service

Agent reserve + conversation id

CCM

EFCX

Persist messages; Agent Desk render

3.1 What the relay does not do

  • Does not originate SIP or bridge calls

  • Does not call StartConversation / create CX conversations

  • Does not store long-term transcript archives (beyond operational logs)

  • Does not replace Voice Connector APISIX NodePort / gateway install work

4. Call Lifecycle and Gating

4.1 Trigger

  1. ESL CHANNEL_BRIDGE with A and B channel UUIDs

  2. Resolve EFSwitch domain_name → tenant gate (TMB_DOMAIN_ALLOWLIST)

  3. Classify call type (inbound / manual outbound / campaign); apply TMB_ENABLE_* flags

  4. Optional inbound DID filter (TMB_ALLOWLIST; * = all DIDs on allowed tenants)

  5. If TMB_MODE=observe, log only; if active, start TX (unless kill file DISABLE.azure exists)

  6. Enforce TMB_MAX_CONCURRENT

4.2 Layout

TMB_TX_LAYOUT

Behavior

dual_mono (default)

One mono Azure session per leg → 2 streams per call

stereo

Single stereo path — use only when validated for the deployment

4.3 Finals → CCM

  • Only final recognition results are posted as conversation messages

  • Payload shape matches prior Jambonz apps: conversation id, header.channelData, sender CONNECTOR vs AGENT, body.type=PLAIN

4.4 Hangup and recovery

  • On hangup / channel destroy, stop media-bugs once per call

  • Mid-call relay start recovers already-bridged calls (recover: CHANNEL_BRIDGE attach)

  • Stopping the systemd unit stops live media-bugs; starting again re-attaches bridged calls when recovery is enabled

5. Multi-Tenant CCM Routing

CCM base = https://{fs_domain_name}.{CCM_ROOT_DOMAIN}/ccm

Example: FS domain_name=thirdparty, CCM_ROOT_DOMAIN=intelicx.ai → https://thirdparty.intelicx.ai/ccm.

  • Matches CX IVR / Voice Connector tenant FQDN pattern

  • CCM_BASE_URL is used when dynamic resolution is unavailable

  • Tenant header tenantid is set when required by the deployment

6. APISIX / Keycloak Auth (CCC-2331)

Auth is opt-in and shared across Azure, Deepgram, and the relay/ package via CxApiAuth.

Mode

Behavior

AUTH_ENABLED=false (default)

No Authorization header — legacy / non-APISIX

AUTH_ENABLED=true

Require API_USERNAME + API_PASS; fail fast at startup if missing

6.1 Token flow

  1. Derive tenant FQDN from CCM base (…/ccm stripped) or fallback base

  2. POST {fqdn}/unified-admin/keycloakLogin with JSON {username, password}

  3. Read data.token; cache per FQDN

  4. Attach Authorization: Bearer <token> on CCM HTTP calls

  5. On 401: invalidate cache, refresh token once, retry the request once

  6. Log markers: AUTH token acquired, AUTH token cache cleared, CX request unauthorized (401)

This mirrors Voice Connector (ecx_generic_connector) env names and login path so RMT can reuse the same Keycloak Expertflow-realm API user (admin + default roles).

6.2 Failure modes

Observation

Typical cause

CCM 401 with auth off

APISIX enabled on EFCX; enable auth env

AUTH token request failed

Bad credentials, wrong FQDN, unified-admin unreachable

AUTH_ENABLED but CCM FQDN unresolved

Neither dynamic nor fallback CCM base usable

Token OK but wrong desk

Conversation id / SI mapping — not auth

7. Deployment Topology

Item

Path / unit

Relay app

/root/transcription_media_bug

Config

/root/transcription_media_bug/.env.azure

Module

/var/lib/freeswitch/tmb/mod_azure_transcribe.so

Speech libs

/var/lib/freeswitch/tmb/lib/

Unit

tmb-relay-azure.service

Autoload drop-in

freeswitch.service.d/tmb-azure-autoload.conf

Hard rules: Keep Speech SDK only under the tmb lib path — never /usr/lib. Do not make install EFSwitch solely for this module. Do not run Deepgram and Azure relays on the same live traffic.

8. Operational Configuration (key knobs)

Key

Role

TMB_MODE

observe | active

TMB_DOMAIN_ALLOWLIST

FS tenant domains allowed

TMB_ENABLE_INBOUND / MANUAL_OUTBOUND / CAMPAIGN

Call-type gates

TMB_ALLOWLIST

Optional inbound DID filter (* = all)

TMB_MAX_CONCURRENT

Cap concurrent transcribed calls

TMB_TX_LAYOUT

dual_mono (default)

AZURE_*

Speech key, region/endpoint, language

ESL_HOST / ESL_PORT / ESL_PASSWORD

Local EFSwitch ESL

CCM_ROOT_DOMAIN / CCM_BASE_URL

CCM routing

AUTH_ENABLED / API_USERNAME / API_PASS

APISIX Bearer

Full RMT steps: Set up Azure live call transcription on an EFSwitch server

9. Capacity and Sizing Notes

  • Each bridged call in dual_mono = 2 Azure concurrent recognition streams

  • Plan Azure Speech quota and EFSwitch CPU for 2 × concurrent bridged calls

  • Start with a conservative TMB_MAX_CONCURRENT (for example 3–10), raise after soak / load test

  • Relay CPU/RAM is usually small vs EFSwitch + Speech SDK in-process cost

  • Prefer horizontal Media Server capacity and Azure quota over multi-worker tricks inside the relay

10. Security Considerations

  • Secrets live in .env.azure (mode 600 recommended); never commit keys or API passwords

  • With APISIX on, use a dedicated Keycloak API user — not personal admin logins in shared docs

  • Transcripts and ANI are PII; restrict log retention and Agent Desk access via CX roles

  • ESL is local-only by design; do not expose 8021 publicly

11. Observability

Marker

Meaning

CHANNEL_BRIDGE / recover: CHANNEL_BRIDGE attach

TX attach path

FINAL

ASR final received

CCM_ROUTE

Resolved conv / svc / ani / tenant

AUTH token acquired

Keycloak login succeeded

CCM ok status=200

Message accepted

CCM + 401 / non-retry

Auth or CCM rejection

Path

Role

This architecture (media-bug + Azure)

Current EFSwitch-native live TX

Deepgram variant (tmb_app.py)

Same ESL→CCM pattern; different ASR module

Jambonz Real-Time Transcription

Legacy eavesdrop / /sipRec path

CCC-2329

Media-bug transcription implementation

CCC-2331

APISIX / CCM auth for transcription