FreeSwitch錄音模組研究
注: 本文的原始碼基於freeswitch V1.6.20。
FreeSwitch中,錄音相關的App是由mod_dptools模組提供的。大部分實現程式碼都在mod_dptools.c中。
App的定義通過巨集SWITCH_ADD_APP新增的。在mod_dptools.c的第6345行:
SWITCH_ADD_APP(app_interface, "stop_record_session", "Stop Record Session", STOP_SESS_REC_DESC, stop_record_session_function, "<path>", SAF_NONE); SWITCH_ADD_APP(app_interface, "record_session", "Record Session", SESS_REC_DESC, record_session_function, "<path> [+<timeout>]", SAF_MEDIA_TAP); SWITCH_ADD_APP(app_interface, "record_session_mask", "Mask audio in recording", SESS_REC_MASK_DESC, record_session_mask_function, "<path>", SAF_MEDIA_TAP); SWITCH_ADD_APP(app_interface, "record_session_unmask", "Resume recording", SESS_REC_UNMASK_DESC, record_session_unmask_function, "<path>", SAF_MEDIA_TAP); SWITCH_ADD_APP(app_interface, "record", "Record File", "Record a file from the channels input", record_function, "<path> [<time_limit_secs>] [<silence_thresh>] [<silence_hits>]", SAF_NONE); SWITCH_ADD_APP(app_interface, "preprocess", "pre-process", "pre-process", preprocess_session_function, "", SAF_NONE);
可以看到record_session的APP註冊的函式是record_session_function。
record_session_function函式在mod_dptools.c的3109行定義,函式很短:
SWITCH_STANDARD_APP(record_session_function) { char *path = NULL; char *path_end; uint32_t limit = 0; if (zstr(data)) { return; } path = switch_core_session_strdup(session, data); /* Search for a space then a plus followed by only numbers at the end of the path, if found trim any spaces to the left/right of the plus use the left side as the path and right side as a time limit on the recording */ /* if we find a + and the character before it is a space */ if ((path_end = strrchr(path, '+')) && path_end > path && *(path_end - 1) == ' ') { char *limit_start = path_end + 1; /* not at the end and the rest is numbers lets parse out the limit and fix up the path */ if (*limit_start != '\0' && switch_is_number(limit_start) == SWITCH_TRUE) { limit = atoi(limit_start); /* back it off by one character to the char before the + */ path_end--; /* trim spaces to the left of the plus */ while (path_end > path && *path_end == ' ') { path_end--; } *(path_end + 1) = '\0'; } } switch_ivr_record_session(session, path, limit, NULL); }
解析引數後呼叫switch_ivr_record_session()函式。這個函式定義在switch_ivr_async.c的2349行處。這個函式很長,不過前面一大段程式碼都是在處理通道變數並設定錄音相關引數。直到2702行:
if ((status = switch_core_media_bug_add(session, "session_record", file, record_callback, rh, to, flags, &bug)) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error adding media bug for file %s\n", file); if (rh->native) { switch_core_file_close(&rh->in_fh); switch_core_file_close(&rh->out_fh); } else { switch_core_file_close(fh); } return status; }
呼叫switch_core_media_bug_add()添加了一個media bug,回撥函式是record_callback()。對媒體流的監聽是讀寫模式的。
record_callback()函式定義在switch_ivr_async.c的1184行。首先看INIT事件的處理:
case SWITCH_ABC_TYPE_INIT:
{
const char *var = switch_channel_get_variable(channel, "RECORD_USE_THREAD");
if (!rh->native && rh->fh && (zstr(var) || switch_true(var))) {
switch_threadattr_t *thd_attr = NULL;
switch_memory_pool_t *pool = switch_core_session_get_pool(session);
int sanity = 200;
switch_core_session_get_read_impl(session, &rh->read_impl);
switch_mutex_init(&rh->buffer_mutex, SWITCH_MUTEX_NESTED, pool);
switch_threadattr_create(&thd_attr, pool);
switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
switch_thread_create(&rh->thread, thd_attr, recording_thread, bug, pool);
while(--sanity > 0 && !rh->thread_ready) {
switch_yield(10000);
}
}
if (switch_event_create(&event, SWITCH_EVENT_RECORD_START) == SWITCH_STATUS_SUCCESS) {
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Record-File-Path", rh->file);
switch_channel_event_set_data(channel, event);
switch_event_fire(&event);
}
rh->silence_time = switch_micro_time_now();
rh->silence_timeout_ms = rh->initial_timeout_ms;
rh->speech_detected = SWITCH_FALSE;
rh->completion_cause = NULL;
switch_core_session_get_read_impl(session, &rh->read_impl);
}
break;
如果通道變數RECORD_USE_THREAD為真,開啟一個處理執行緒,執行緒函式為recording_thread(),處理核心快取和檔案轉存。否則,直接寫檔案。同時,觸發一個SWITCH_EVENT_RECORD_START事件。
如果錄音檔名不帶字尾,那麼會錄製NATIVE檔案,走的是SWITCH_ABC_TYPE_TAP_NATIVE_READ分支,否則,走的是SWITCH_ABC_TYPE_READ_PING分支。
SWITCH_ABC_TYPE_TAP_NATIVE_READ分支中,呼叫switch_core_media_bug_get_native_read_frame()讀取幀,然後呼叫switch_core_file_write()寫檔案。
SWITCH_ABC_TYPE_READ_PING分支,在1438行呼叫switch_core_media_bug_read()讀取幀,在1449行呼叫switch_core_file_write()寫檔案(如果使用核心快取,則是在1447行呼叫switch_buffer_write()快取,不直接寫檔案),其後的程式碼是靜音監測處理相關的邏輯。
SWITCH_ABC_TYPE_CLOSE分支處理結束時的資源回收。、
回頭看看核心快取處理函式recording_thread(),每個錄音緩衝池pre_buffer,將需要寫入檔案的資料寫入到此緩衝內,當緩衝資料大小達到 SWITCH_DEFAULT_FILE_BUFFER_LEN時,核心從緩衝池中獲取資料寫檔案。應用程式可以通過設定通道變數enable_file_write_buffering來設定 SWITCH_DEFAULT_FILE_BUFFER_LEN的大小。