This guide provides an overview of the FreeSWITCH internal architecture, based on core principles and enriched with examples from the actual codebase. It is designed to help developers use the framework effectively.
The FreeSWITCH core functions as a library (libfreeswitch), contained primarily in src/*.c. It provides common abstractions and services used by modules.
- OS Abstraction (
src/switch_apr.c): Wraps the Apache Portable Runtime (APR). Do not use APR directly; useswitch_prefixed functions for threads, sockets, mutexes, and memory management. - RTP Stack (
src/switch_rtp.c): Provides a consistent API for RTP streams, used by SIP (mod_sofia), H.323, etc. - Call Switching (
src/switch_ivr.c,src/switch_ivr_originate.c): High-level API to create sessions (calls) without knowing protocol details. - Media API (
src/switch_ivr_play_say.c): Functions to play files, speak text, and generate tones. - Session Management (
src/switch_core_session.c): Handles call control (answer, hangup) and media I/O.
The switch_ivr_originate function is the primary way to create outbound calls.
/* Example from mod_commands.c */
switch_core_session_t *caller_session = NULL;
switch_call_cause_t cause = SWITCH_CAUSE_NORMAL_CLEARING;
uint32_t timeout = 60;
if (switch_ivr_originate(NULL, &caller_session, &cause, aleg, timeout, NULL, cid_name, cid_num, NULL, NULL, SOF_NONE, NULL, NULL) != SWITCH_STATUS_SUCCESS
|| !caller_session) {
stream->write_function(stream, "-ERR %s\n", switch_channel_cause2str(cause));
return SWITCH_STATUS_FALSE;
}
/* Interaction with the new session... */
switch_core_session_rwunlock(caller_session);The switch_core_session_t structure represents a call leg. It contains a switch_channel_t, which holds protocol-specific data and state.
Each session runs in its own thread, looping through a state machine (CS_NEW, CS_INIT, CS_ROUTING, CS_EXECUTE, CS_EXCHANGE_MEDIA, CS_DESTROY).
To access a session from another thread securely, use switch_core_session_locate to find and lock it by UUID. Always unlock with switch_core_session_rwunlock when done.
/* Example: Locating a session safely (from mod_commands.c) */
switch_core_session_t *xsession;
if ((xsession = switch_core_session_locate(uuid))) {
/* Safe to use xsession here */
switch_channel_event_set_data(switch_core_session_get_channel(xsession), stream->param_event);
/* Always unlock when done! */
switch_core_session_rwunlock(xsession);
}FreeSWITCH uses APR memory pools (switch_memory_pool_t).
- Session Scope: If data belongs to a call, use the session's pool (
switch_core_session_get_pool(session)). It is automatically freed when the session ends. - Global/Long-term: Create a new pool (
switch_core_new_memory_pool) for data that persists beyond a single call. You are responsible for destroying this pool.
switch_core_session_alloc(session, size): Allocate from session pool.switch_core_session_strdup(session, string): Duplicate string in session pool.switch_core_alloc(pool, size): Allocate from a specific pool.switch_safe_free(ptr): Free malloc'd memory (checking for NULL). Note: Does not free pool memory.
/* Example: Allocating struct from session pool (mod_dptools.c) */
struct action_binding *act;
act = switch_core_session_alloc(session, sizeof(*act));
/* The memory for 'act' will be automatically freed when the session ends.
No need to call free(act). */Use FreeSWITCH utility functions for safety and consistency.
switch_copy_string(dest, src, size): Safe replacement forstrncpy.switch_set_string(dest, src): Macro usingsizeof(dest).switch_safe_atoi(str, default_val): Robust string-to-int conversion.
switch_true(str): Returns true for "true", "on", "yes", "enabled", "active", "allow", "j".switch_false(str): Returns true for "false", "off", "no", "disabled", "inactive", "disallow", "k".SWITCH_TRUE/SWITCH_FALSE: Standard macros.
FreeSWITCH modules are dynamic libraries that register interfaces with the core. The mod_skel application provides a standard template.
Every module must define load/shutdown functions and the module definition macro.
/* Standard Module Macros */
SWITCH_MODULE_LOAD_FUNCTION(mod_skel_load);
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_skel_shutdown);
SWITCH_MODULE_DEFINITION(mod_skel, mod_skel_load, mod_skel_shutdown, NULL);
/* Load Function: Registers the module's interface */
SWITCH_MODULE_LOAD_FUNCTION(mod_skel_load)
{
switch_application_interface_t *app_interface;
switch_api_interface_t *api_interface;
/* Connect to the core */
*module_interface = switch_loadable_module_create_module_interface(pool, modname);
/* Register an Application (Dialplan Action) */
SWITCH_ADD_APP(app_interface, "skel", "Skeleton Application", "Description", skel_function, "syntax", SAF_NONE);
/* Register an API (Console Command) */
SWITCH_ADD_API(api_interface, "skel", "Skeleton API", skel_api_function, "syntax");
return SWITCH_STATUS_SUCCESS;
}SWITCH_STANDARD_API(skel_api_function)
{
stream->write_function(stream, "+OK Skeleton Module\n");
return SWITCH_STATUS_SUCCESS;
}Beyond standard applications and APIs, FreeSWITCH support specialized interfaces for ASR and TTS. src/mod/applications/mod_test/mod_test.c is a great reference.
To implement a TTS engine, register a switch_speech_interface_t.
/* Macros and Definitions */
SWITCH_MODULE_LOAD_FUNCTION(mod_test_load) {
switch_speech_interface_t *speech_interface;
/* ... init module interface ... */
speech_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_SPEECH_INTERFACE);
speech_interface->interface_name = "test_tts";
speech_interface->speech_open = test_speech_open;
speech_interface->speech_close = test_speech_close;
speech_interface->speech_feed_tts = test_speech_feed_tts; /* Input text */
speech_interface->speech_read_tts = test_speech_read_tts; /* Output audio */
return SWITCH_STATUS_SUCCESS;
}
static switch_status_t test_speech_feed_tts(switch_speech_handle_t *sh, char *text, switch_speech_flag_t *flags) {
/* Process text input here */
return SWITCH_STATUS_SUCCESS;
}To implement an ASR engine, register a switch_asr_interface_t.
SWITCH_MODULE_LOAD_FUNCTION(mod_test_load) {
switch_asr_interface_t *asr_interface;
/* ... init module interface ... */
asr_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_ASR_INTERFACE);
asr_interface->interface_name = "test_asr";
asr_interface->asr_open = test_asr_open;
asr_interface->asr_feed = test_asr_feed; /* Receive audio */
asr_interface->asr_check_results = test_asr_check_results;
asr_interface->asr_get_results = test_asr_get_results;
return SWITCH_STATUS_SUCCESS;
}Media bugs allow you to tap into a session's audio stream (RTP) for reading or writing.
Use switch_core_media_bug_add to attach a callback to a session.
/* Example based on mod_audio_pipe.c */
/* 1. Define the callback */
static switch_bool_t capture_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type)
{
switch_byte_t data[SWITCH_RECOMMENDED_BUFFER_SIZE];
switch (type) {
case SWITCH_ABC_TYPE_INIT:
break;
case SWITCH_ABC_TYPE_READ: /* Triggered when audio is read */
if (switch_core_media_bug_read(bug, (switch_frame_t *) &frame, SWITCH_TRUE) == SWITCH_STATUS_SUCCESS) {
/* Process frame.data here */
}
break;
case SWITCH_ABC_TYPE_CLOSE:
break;
}
return SWITCH_TRUE;
}
/* 2. Attach the bug to a session */
switch_media_bug_t *bug;
switch_media_bug_flag_t flags = SMBF_READ_STREAM | SMBF_WRITE_STREAM | SMBF_READ_PING;
switch_core_media_bug_add(session, "my_bug", NULL, capture_callback, user_data, 0, flags, &bug);To send audio back to the caller (e.g., from a WebSocket or TTS engine) while simultaneously recording/processing their input, you must use the SWITCH_ABC_TYPE_WRITE_REPLACE callback in your media bug.
By default, the write path sends audio from the core (e.g., the other leg of the call) to the session. WRITE_REPLACE allows your module to intercept this frame and replace it with your own data (or mix your data into it).
/* Pseudocode based on mod_audio_pipe */
static switch_bool_t capture_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type)
{
switch (type) {
/* ... INIT and READ (for recording) ... */
case SWITCH_ABC_TYPE_WRITE_REPLACE:
{
switch_frame_t *frame = switch_core_media_bug_get_write_replace_frame(bug);
/* Logic:
1. Check if we have audio in our own buffer (e.g. from WebSocket)
2. If yes, replace 'frame->data' with our data
OR mix our data with 'frame->data'
3. If no, do nothing (let original audio pass through)
*/
if (has_data_to_send(user_data)) {
/* Injecting audio */
switch_core_media_bug_set_write_replace_frame(bug, my_custom_frame);
}
}
break;
}
return SWITCH_TRUE;
}
/* Important: You must set the SMBF_WRITE_REPLACE flag when adding the bug */
flags |= SMBF_WRITE_REPLACE;While many structs are opaque, understanding their content helps in debugging and advanced module development.
Represents the "call leg". It binds the channel to the core resources.
- pool: The memory pool for this session.
- channel: Pointer to the associated
switch_channel_t. - thread: The thread object running this session's state machine.
- read_codec / write_codec: Pointers to the active codecs.
- media_handle: Manages RTP/SRTP resources.
- private_info: Array of void pointers for modules to store private data.
Represents the state and protocol data of the call.
- name: The channel name (e.g.,
sofia/internal/1000@1.2.3.4). - state: Current state (CS_INIT, CS_EXECUTE, etc.).
- caller_profile: Contains Caller ID, Destination Number, Context, etc.
- variables: Key-value pairs (Channel Variables).
- flags: Status flags (e.g.,
CF_ANSWERED,CF_BRIDGED).