也谈如何利用FreeSwitch的media_bug

阅读 1019 次  0 条评论
一、前言 由于要在FreeSwitch中做话务实时质检,故而,我们尝试通过FreeSwitch的record_session这一块来实现。 何为话务实时质检?就是实时的把主、被叫的语音沟通数据(IP化后,大家的视频、音频都是数字化数据)采集到,并且进行必要的分隔,然后送给asr引擎进行识别,识别到一些好的,不好的句子后,可以立即通短到相关人员。 二、技术实现 注册基于media_bug的FreeSwitch application ```c SWITCH_ADD_APP(app_interface, "record_session", "Record Session", SESS_REC_DESC, record_session_function, "<path> [+<timeout>]", SAF_MEDIA_TAP); ``` 函数实现 ```c 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); if ((path_end = strrchr(path, '+')) && path_end > path && *(path_end - 1) == ' ') { char *limit_start = path_end + 1; if (*limit_start != '\0' && switch_is_number(limit_start) == SWITCH_TRUE) { limit = atoi(limit_start); path_end--; while (path_end > path && *path_end == ' ') { path_end--; } *(path_end + 1) = '\0'; } } switch_ivr_record_session(session, path, limit, NULL); } ``` 3. 功能实现函数 ```c SWITCH_DECLARE(switch_status_t) switch_ivr_record_session(switch_core_session_t *session, char *file, uint32_t limit, switch_file_handle_t *fh) { switch_channel_t *channel = switch_core_session_get_channel(session); const char *p; const char *vval; switch_media_bug_t *bug; switch_status_t status; time_t to = 0; switch_media_bug_flag_t flags = SMBF_READ_STREAM | SMBF_WRITE_STREAM | SMBF_READ_PING; uint8_t channels; switch_codec_implementation_t read_impl = { 0 }; struct record_helper *rh = NULL; int file_flags = SWITCH_FILE_FLAG_WRITE | SWITCH_FILE_DATA_SHORT; switch_bool_t hangup_on_error = SWITCH_FALSE; char *file_path = NULL; char *ext; char *in_file = NULL, *out_file = NULL; if ((p = switch_channel_get_variable(channel, "RECORD_HANGUP_ON_ERROR"))) { hangup_on_error = switch_true(p); } if ((status = switch_channel_pre_answer(channel)) != SWITCH_STATUS_SUCCESS) { return SWITCH_STATUS_FALSE; } if (!switch_channel_media_up(channel) || !switch_core_session_get_read_codec(session)) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Can not record session. Media not enabled on channel\n"); return SWITCH_STATUS_FALSE; } switch_core_session_get_read_impl(session, &read_impl); channels = read_impl.number_of_channels; if ((bug = switch_channel_get_private(channel, file))) { if (switch_true(switch_channel_get_variable(channel, "RECORD_TOGGLE_ON_REPEAT"))) { return switch_ivr_stop_record_session(session, file); } switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Already recording [%s]\n", file); return SWITCH_STATUS_SUCCESS; } if ((p = switch_channel_get_variable(channel, "RECORD_CHECK_BRIDGE")) && switch_true(p)) { switch_core_session_t *other_session; int exist = 0; switch_status_t rstatus = SWITCH_STATUS_SUCCESS; if (switch_core_session_get_partner(session, &other_session) == SWITCH_STATUS_SUCCESS) { switch_channel_t *other_channel = switch_core_session_get_channel(other_session); if ((bug = switch_channel_get_private(other_channel, file))) { if (switch_true(switch_channel_get_variable(other_channel, "RECORD_TOGGLE_ON_REPEAT"))) { rstatus = switch_ivr_stop_record_session(other_session, file); } else { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(other_session), SWITCH_LOG_WARNING, "Already recording [%s]\n", file); } exist = 1; } switch_core_session_rwunlock(other_session); } if (exist) { return rstatus; } } if (!fh) { if (!(fh = switch_core_session_alloc(session, sizeof(*fh)))) { return SWITCH_STATUS_MEMERR; } } if ((p = switch_channel_get_variable(channel, "RECORD_WRITE_ONLY")) && switch_true(p)) { flags &= ~SMBF_READ_STREAM; flags |= SMBF_WRITE_STREAM; } if ((p = switch_channel_get_variable(channel, "RECORD_READ_ONLY")) && switch_true(p)) { flags &= ~SMBF_WRITE_STREAM; flags |= SMBF_READ_STREAM; } if (channels == 1) { /* if leg is already stereo this feature is not available */ if ((p = switch_channel_get_variable(channel, "RECORD_STEREO")) && switch_true(p)) { flags |= SMBF_STEREO; flags &= ~SMBF_STEREO_SWAP; channels = 2; } if ((p = switch_channel_get_variable(channel, "RECORD_STEREO_SWAP")) && switch_true(p)) { flags |= SMBF_STEREO; flags |= SMBF_STEREO_SWAP; channels = 2; } } if ((p = switch_channel_get_variable(channel, "RECORD_ANSWER_REQ")) && switch_true(p)) { flags |= SMBF_ANSWER_REQ; } if ((p = switch_channel_get_variable(channel, "RECORD_BRIDGE_REQ")) && switch_true(p)) { flags |= SMBF_BRIDGE_REQ; } if ((p = switch_channel_get_variable(channel, "RECORD_APPEND")) && switch_true(p)) { file_flags |= SWITCH_FILE_WRITE_APPEND; } fh->samplerate = 0; if ((vval = switch_channel_get_variable(channel, "record_sample_rate"))) { int tmp = 0; tmp = atoi(vval); if (switch_is_valid_rate(tmp)) { fh->samplerate = tmp; } } if (!fh->samplerate) { fh->samplerate = read_impl.actual_samples_per_second; } fh->channels = channels; if ((vval = switch_channel_get_variable(channel, "enable_file_write_buffering"))) { int tmp = atoi(vval); if (tmp > 0) { fh->pre_buffer_datalen = tmp; } else if (switch_true(vval)) { fh->pre_buffer_datalen = SWITCH_DEFAULT_FILE_BUFFER_LEN; } } else { fh->pre_buffer_datalen = SWITCH_DEFAULT_FILE_BUFFER_LEN; } if (!switch_is_file_path(file)) { char *tfile = NULL; char *e; const char *prefix; prefix = switch_channel_get_variable(channel, "sound_prefix"); if (!prefix) { prefix = SWITCH_GLOBAL_dirs.base_dir; } if (*file == '[') { tfile = switch_core_session_strdup(session, file); if ((e = switch_find_end_paren(tfile, '[', ']'))) { *e = '\0'; file = e + 1; } else { tfile = NULL; } } else { file_path = switch_core_session_sprintf(session, "%s%s%s", prefix, SWITCH_PATH_SEPARATOR, file); } file = switch_core_session_sprintf(session, "%s%s%s%s%s", switch_str_nil(tfile), tfile ? "]" : "", prefix, SWITCH_PATH_SEPARATOR, file); } else { file_path = switch_core_session_strdup(session, file); } if (file_path && !strstr(file_path, SWITCH_URL_SEPARATOR)) { char *p; char *path = switch_core_session_strdup(session, file_path); if ((p = strrchr(path, *SWITCH_PATH_SEPARATOR))) { *p = '\0'; if (switch_dir_make_recursive(path, SWITCH_DEFAULT_DIR_PERMS, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error creating %s\n", path); return SWITCH_STATUS_GENERR; } } else { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error finding the folder path section in '%s'\n", path); path = NULL; } } rh = switch_core_session_alloc(session, sizeof(*rh)); if ((ext = strrchr(file, '.'))) { ext++; if (switch_core_file_open(fh, file, channels, read_impl.actual_samples_per_second, file_flags, NULL) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error opening %s\n", file); if (hangup_on_error) { switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); switch_core_session_reset(session, SWITCH_TRUE, SWITCH_TRUE); } return SWITCH_STATUS_GENERR; } } else { int tflags = 0; ext = read_impl.iananame; in_file = switch_core_session_sprintf(session, "%s-in.%s", file, ext); out_file = switch_core_session_sprintf(session, "%s-out.%s", file, ext); rh->in_fh.pre_buffer_datalen = rh->out_fh.pre_buffer_datalen = fh->pre_buffer_datalen; channels = 1; switch_set_flag(&rh->in_fh, SWITCH_FILE_NATIVE); switch_set_flag(&rh->out_fh, SWITCH_FILE_NATIVE); if (switch_core_file_open(&rh->in_fh, in_file, channels, read_impl.actual_samples_per_second, file_flags, NULL) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error opening %s\n", in_file); if (hangup_on_error) { switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); switch_core_session_reset(session, SWITCH_TRUE, SWITCH_TRUE); } return SWITCH_STATUS_GENERR; } if (switch_core_file_open(&rh->out_fh, out_file, channels, read_impl.actual_samples_per_second, file_flags, NULL) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error opening %s\n", out_file); switch_core_file_close(&rh->in_fh); if (hangup_on_error) { switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); switch_core_session_reset(session, SWITCH_TRUE, SWITCH_TRUE); } return SWITCH_STATUS_GENERR; } rh->native = 1; fh = NULL; if ((flags & SMBF_WRITE_STREAM)) { tflags |= SMBF_TAP_NATIVE_WRITE; } if ((flags & SMBF_READ_STREAM)) { tflags |= SMBF_TAP_NATIVE_READ; } flags = tflags; } if ((p = switch_channel_get_variable(channel, "RECORD_TITLE"))) { vval = (const char *) switch_core_session_strdup(session, p); if (fh) switch_core_file_set_string(fh, SWITCH_AUDIO_COL_STR_TITLE, vval); switch_channel_set_variable(channel, "RECORD_TITLE", NULL); } if ((p = switch_channel_get_variable(channel, "RECORD_COPYRIGHT"))) { vval = (const char *) switch_core_session_strdup(session, p); if (fh) switch_core_file_set_string(fh, SWITCH_AUDIO_COL_STR_COPYRIGHT, vval); switch_channel_set_variable(channel, "RECORD_COPYRIGHT", NULL); } if ((p = switch_channel_get_variable(channel, "RECORD_SOFTWARE"))) { vval = (const char *) switch_core_session_strdup(session, p); if (fh) switch_core_file_set_string(fh, SWITCH_AUDIO_COL_STR_SOFTWARE, vval); switch_channel_set_variable(channel, "RECORD_SOFTWARE", NULL); } if ((p = switch_channel_get_variable(channel, "RECORD_ARTIST"))) { vval = (const char *) switch_core_session_strdup(session, p); if (fh) switch_core_file_set_string(fh, SWITCH_AUDIO_COL_STR_ARTIST, vval); switch_channel_set_variable(channel, "RECORD_ARTIST", NULL); } if ((p = switch_channel_get_variable(channel, "RECORD_COMMENT"))) { vval = (const char *) switch_core_session_strdup(session, p); if (fh) switch_core_file_set_string(fh, SWITCH_AUDIO_COL_STR_COMMENT, vval); switch_channel_set_variable(channel, "RECORD_COMMENT", NULL); } if ((p = switch_channel_get_variable(channel, "RECORD_DATE"))) { vval = (const char *) switch_core_session_strdup(session, p); if (fh) switch_core_file_set_string(fh, SWITCH_AUDIO_COL_STR_DATE, vval); switch_channel_set_variable(channel, "RECORD_DATE", NULL); } if (limit) { to = switch_epoch_time_now(NULL) + limit; } rh->fh = fh; rh->file = switch_core_session_strdup(session, file); rh->packet_len = read_impl.decoded_bytes_per_packet; if (file_flags & SWITCH_FILE_WRITE_APPEND) { rh->min_sec = 3; } if ((p = switch_channel_get_variable(channel, "RECORD_MIN_SEC"))) { int tmp = atoi(p); if (tmp >= 0) { rh->min_sec = tmp; } } if ((p = switch_channel_get_variable(channel, "RECORD_INITIAL_TIMEOUT_MS"))) { int tmp = atoi(p); if (tmp >= 0) { rh->initial_timeout_ms = tmp; rh->silence_threshold = 200; } } if ((p = switch_channel_get_variable(channel, "RECORD_FINAL_TIMEOUT_MS"))) { int tmp = atoi(p); if (tmp >= 0) { rh->final_timeout_ms = tmp; rh->silence_threshold = 200; } } if ((p = switch_channel_get_variable(channel, "RECORD_SILENCE_THRESHOLD"))) { int tmp = atoi(p); if (tmp >= 0) { rh->silence_threshold = tmp; } } rh->hangup_on_error = hangup_on_error; 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; } if ((p = switch_channel_get_variable(channel, "RECORD_PRE_BUFFER_FRAMES"))) { int tmp = atoi(p); if (tmp > 0) { switch_core_media_bug_set_pre_buffer_framecount(bug, tmp); } } else { switch_core_media_bug_set_pre_buffer_framecount(bug, 25); } switch_channel_set_private(channel, file, bug); return SWITCH_STATUS_SUCCESS; } ``` 4. 媒体处理-回调 ```c static switch_bool_t record_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type) { ...... switch (type) { case SWITCH_ABC_TYPE_INIT: break; case SWITCH_ABC_TYPE_TAP_NATIVE_READ: break; case SWITCH_ABC_TYPE_TAP_NATIVE_WRITE: break; case SWITCH_ABC_TYPE_CLOSE: break; case SWITCH_ABC_TYPE_READ_PING: break; case SWITCH_ABC_TYPE_WRITE: default: break; } return SWITCH_TRUE; } ``` 三、 技术回顾 这里使用了一个很重要的FreeSwitch的技术特性-media_bug,此bug非是平常所说的software bug(臭虫),而是指对media的复用,bug这里应翻译为窃取,因为数字化,所以作为复用或复制会更好些。从而可以让用户使用这个功能来完成要想的语音通话实时质检,监听,语音识别,回铃检测等等业务功能。

0条回复

主题回复:

(您需要 登录 后才能回复 没有账号 ?)
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet