也谈如何利用FreeSwitch的media_bug
一、前言
由于要在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这里应翻译为窃取,因为数字化,所以作为复用或复制会更好些。从而可以让用户使用这个功能来完成要想的语音通话实时质检,监听,语音识别,回铃检测等等业务功能。
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
主题回复:
(您需要 登录 后才能回复 没有账号 ?)