Skip to content

Instantly share code, notes, and snippets.

@cyrenity
Created January 14, 2026 10:45
Show Gist options
  • Select an option

  • Save cyrenity/6614894c138942dcdf6168bbc16fc7fc to your computer and use it in GitHub Desktop.

Select an option

Save cyrenity/6614894c138942dcdf6168bbc16fc7fc to your computer and use it in GitHub Desktop.
FreeSWITCH Dev notes

FreeSWITCH Development Guide

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.

Core Architecture

The FreeSWITCH core functions as a library (libfreeswitch), contained primarily in src/*.c. It provides common abstractions and services used by modules.

Key Components

  • OS Abstraction (src/switch_apr.c): Wraps the Apache Portable Runtime (APR). Do not use APR directly; use switch_ 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.

Example: Originating a Call

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);

Session and Channel Management

The switch_core_session_t structure represents a call leg. It contains a switch_channel_t, which holds protocol-specific data and state.

Threading Model

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).

Locking and Access

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);
}

Memory Management

FreeSWITCH uses APR memory pools (switch_memory_pool_t).

Guidelines

  • 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.

Key Functions

  • 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). */

Strings and Booleans

Use FreeSWITCH utility functions for safety and consistency.

String Utils

  • switch_copy_string(dest, src, size): Safe replacement for strncpy.
  • switch_set_string(dest, src): Macro using sizeof(dest).
  • switch_safe_atoi(str, default_val): Robust string-to-int conversion.

Booleans

  • 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.

Module Development

FreeSWITCH modules are dynamic libraries that register interfaces with the core. The mod_skel application provides a standard template.

Basic Structure (src/mod/applications/mod_skel/mod_skel.c)

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;
}

API Command Example

SWITCH_STANDARD_API(skel_api_function)
{
    stream->write_function(stream, "+OK Skeleton Module\n");
    return SWITCH_STATUS_SUCCESS;
}

Advanced Module Types

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.

Text-To-Speech (TTS) Interface

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;
}

Automatic Speech Recognition (ASR) Interface

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 Processing (Media Bugs)

Media bugs allow you to tap into a session's audio stream (RTP) for reading or writing.

Attaching a Media Bug

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);

Bi-Directional Audio Streaming

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.

The Write Replace Pattern

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;

Data Structures Deep Dive

While many structs are opaque, understanding their content helps in debugging and advanced module development.

switch_core_session_t (Session)

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.

switch_channel_t (Channel)

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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment