FreeSWITCH中文网,电话机器人开发网 ,微信订阅号:

FreeSWITCH及VOIP,Openser,电话机器人等产品中文技术资讯、交流、沟通、培训、咨询、服务一体化网络。QQ群:293697898

也谈如何利用FreeSwitch的media_bug


tags:FreeSwitch模块 media_bug 质检 创建时间:2019-03-05 17:53:09

一、前言

   由于要在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; } ```

  1. 媒体处理-回调

```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这里应翻译为窃取,因为数字化,所以作为复用或复制会更好些。从而可以让用户使用这个功能来完成要想的语音通话实时质检,监听,语音识别,回铃检测等等业务功能。


上海老李,QQ:1354608370,FreeSWITCH QQ群: