1. 程式人生 > >FreeSwitch錄音模組研究

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的大小。