之前有重点介绍过,我们的智能外呼+智能ivr+本次的智能视频ivr,均采用了freeswitch 二次开发自定义application完成的。
在我们继续如何实现智能视频IVR之前,我们来详细解读一下media bug。
freeswitch使用media bug来实现旁路输出媒体,在录音、监听、ASR、信号检测等等场景都使用到它。
我们看一下switch_core_media_bug_add 用来添加一个bug, freeswitch eavesdrop 源码中是如何使用的呢?
if (switch_core_media_bug_add(tsession, "eavesdrop", uuid,
eavesdrop_callback, ep, 0,
read_flags | write_flags | SMBF_READ_PING | SMBF_THREAD_LOCK | SMBF_NO_PAUSE,
&bug) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Cannot attach bug\n");
goto end;
}
这里的 callback函数
static switch_bool_t eavesdrop_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type)
{
struct eavesdrop_pvt *ep = (struct eavesdrop_pvt *) user_data;
uint8_t data[SWITCH_RECOMMENDED_BUFFER_SIZE];
switch_frame_t frame = { 0 };
switch_core_session_t *session = switch_core_media_bug_get_session(bug);
switch_channel_t *e_channel = switch_core_session_get_channel(ep->eavesdropper);
int show_spy = 0;
switch_frame_t *nframe = NULL;
frame.data = data;
frame.buflen = SWITCH_RECOMMENDED_BUFFER_SIZE;
show_spy = switch_core_media_bug_test_flag(bug, SMBF_SPY_VIDEO_STREAM) || switch_core_media_bug_test_flag(bug, SMBF_SPY_VIDEO_STREAM_BLEG);
if (show_spy) {
if (!ep->set_decoded_read) {
ep->set_decoded_read = 1;
switch_channel_set_flag_recursive(e_channel, CF_VIDEO_DECODED_READ);
switch_core_session_request_video_refresh(ep->eavesdropper);
}
} else {
if (ep->set_decoded_read) {
ep->set_decoded_read = 0;
switch_channel_clear_flag_recursive(e_channel, CF_VIDEO_DECODED_READ);
switch_core_session_request_video_refresh(ep->eavesdropper);
}
}
switch (type) {
case SWITCH_ABC_TYPE_INIT:
if (switch_core_media_bug_test_flag(bug, SMBF_READ_VIDEO_STREAM) ||
switch_core_media_bug_test_flag(bug, SMBF_WRITE_VIDEO_STREAM) ||
switch_core_media_bug_test_flag(bug, SMBF_READ_VIDEO_PING)) {
switch_core_session_set_video_read_callback(ep->eavesdropper, video_eavesdrop_callback, (void *)bug);
switch_channel_set_flag_recursive(switch_core_session_get_channel(session), CF_VIDEO_DECODED_READ);
}
break;
case SWITCH_ABC_TYPE_CLOSE:
if (ep->set_decoded_read) {
switch_channel_clear_flag_recursive(e_channel, CF_VIDEO_DECODED_READ);
}
if (switch_core_media_bug_test_flag(bug, SMBF_READ_VIDEO_STREAM) ||
switch_core_media_bug_test_flag(bug, SMBF_WRITE_VIDEO_STREAM) ||
switch_core_media_bug_test_flag(bug, SMBF_READ_VIDEO_PING)) {
switch_core_session_set_video_read_callback(ep->eavesdropper, NULL, NULL);
}
switch_channel_clear_flag_recursive(switch_core_session_get_channel(session), CF_VIDEO_DECODED_READ);
break;
case SWITCH_ABC_TYPE_TAP_NATIVE_WRITE:
nframe = switch_core_media_bug_get_native_write_frame(bug);
break;
case SWITCH_ABC_TYPE_TAP_NATIVE_READ:
nframe = switch_core_media_bug_get_native_read_frame(bug);
break;
case SWITCH_ABC_TYPE_WRITE:
break;
case SWITCH_ABC_TYPE_READ_PING:
if (ep->buffer) {
if (switch_core_media_bug_read(bug, &frame, SWITCH_FALSE) != SWITCH_STATUS_FALSE) {
switch_buffer_lock(ep->buffer);
switch_buffer_zwrite(ep->buffer, frame.data, frame.datalen);
switch_buffer_unlock(ep->buffer);
}
}
break;
case SWITCH_ABC_TYPE_READ:
break;
case SWITCH_ABC_TYPE_READ_REPLACE:
{
if (switch_test_flag(ep, ED_MUX_READ)) {
switch_frame_t *rframe = switch_core_media_bug_get_read_replace_frame(bug);
if (switch_buffer_inuse(ep->r_buffer) >= rframe->datalen) {
uint32_t bytes;
int channels = rframe->channels ? rframe->channels : 1;
switch_buffer_lock(ep->r_buffer);
bytes = (uint32_t) switch_buffer_read(ep->r_buffer, ep->data, rframe->datalen);
rframe->datalen = switch_merge_sln(rframe->data, rframe->samples, (int16_t *) ep->data, bytes / 2, channels) * 2 * channels;
rframe->samples = rframe->datalen / 2;
ep->demux_frame.data = ep->data;
ep->demux_frame.datalen = bytes;
ep->demux_frame.samples = bytes / 2;
ep->demux_frame.channels = rframe->channels;
switch_buffer_unlock(ep->r_buffer);
switch_core_media_bug_set_read_replace_frame(bug, rframe);
switch_core_media_bug_set_read_demux_frame(bug, &ep->demux_frame);
}
}
}
break;
case SWITCH_ABC_TYPE_WRITE_REPLACE:
{
if (switch_test_flag(ep, ED_MUX_WRITE)) {
switch_frame_t *rframe = switch_core_media_bug_get_write_replace_frame(bug);
if (switch_buffer_inuse(ep->w_buffer) >= rframe->datalen) {
uint32_t bytes;
int channels = rframe->channels ? rframe->channels : 1;
switch_buffer_lock(ep->w_buffer);
bytes = (uint32_t) switch_buffer_read(ep->w_buffer, data, rframe->datalen);
rframe->datalen = switch_merge_sln(rframe->data, rframe->samples, (int16_t *) data, bytes / 2, channels) * 2 * channels;
rframe->samples = rframe->datalen / 2;
switch_buffer_unlock(ep->w_buffer);
switch_core_media_bug_set_write_replace_frame(bug, rframe);
}
}
}
break;
case SWITCH_ABC_TYPE_READ_VIDEO_PING:
case SWITCH_ABC_TYPE_STREAM_VIDEO_PING:
{
if (!bug->video_ping_frame || !bug->video_ping_frame->img) {
break;
}
if (ep->eavesdropper && switch_core_session_read_lock(ep->eavesdropper) == SWITCH_STATUS_SUCCESS) {
if (switch_core_session_write_video_frame(ep->eavesdropper, bug->video_ping_frame, SWITCH_IO_FLAG_NONE, 0) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error writing video to %s\n", switch_core_session_get_name(ep->eavesdropper));
ep->errs++;
if (ep->errs > 10) {
switch_channel_hangup(e_channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
switch_core_session_reset(ep->eavesdropper, SWITCH_TRUE, SWITCH_TRUE);
switch_core_session_rwunlock(ep->eavesdropper);
return SWITCH_FALSE;
}
} else {
ep->errs = 0;
}
switch_core_session_rwunlock(ep->eavesdropper);
}
}
break;
default:
break;
}
if (nframe) {
switch_frame_t frame = *nframe;
uint8_t buf[SWITCH_RECOMMENDED_BUFFER_SIZE] = "";
frame.data = buf;
frame.codec = nframe->codec;
memcpy(frame.data, nframe->data, nframe->datalen);
if (switch_core_session_write_frame(ep->eavesdropper, nframe, SWITCH_IO_FLAG_NONE, 0) != SWITCH_STATUS_SUCCESS) {
return SWITCH_FALSE;
}
}
return SWITCH_TRUE;
}
源码比较长,我们从eavesdrop 的监听就知道了,每当freeswitch读到一个frame时候,都会触发调用callback回调。
我们来浓缩一下callback函数,假设我们自定义一个mediabug,
static void start_media_bug(switch_core_session_t *session) {
switch_media_bug_t *bug = NULL;
if (switch_core_media_bug_add(session, "my_bug", NULL, bug_callback, handler, 0, SMBF_READ_REPLACE, → &bug) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Failed to create media → bug\n");
}
/* save bug to channel for later removal */
switch_channel_set_private(switch_core_session_get_channel(session), "__my_bug__", bug); }
static void stop_media_bug(switch_core_session_t *session) {
/* get saved bug from channel */
switch_media_bug_t *bug = switch_channel_get_private(switch_core_session_get_channel(session), → "__my_bug__", bug);
if (bug) {
switch_core_media_bug_remove(session, &bug);
} }
即使用switch_core_media_bug_add 方法为session添加media bug
那么我们的callback函数应当如下:
static switch_bool_t robot_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type)
{
// switch_codec_t *read_codec;
switch_frame_t *frame;
switch (type) {
case SWITCH_ABC_TYPE_INIT:
break;
case SWITCH_ABC_TYPE_READ_REPLACE:
frame = switch_core_media_bug_get_read_replace_frame(bug);
process_data(frame,bug);
break;
case SWITCH_ABC_TYPE_CLOSE:
break;
default:
break;
}
return SWITCH_TRUE;
}
当有媒体流读入时候,
type SWITCH_ABC_TYPE_READ_REPLACE ,同样的 SWITCH_ABC_TYPE_INIT, 和 SWITCH_ABC_TYPE_CLOSE正如他字面意思描述那样,代表着初始化和关闭。
我们就可以调用 frame =switch_core_media_bug_get_read_replace_frame(bug); 将frame获取到,并进行我们自己的处理。
因此实现媒体流的旁路输出,是一个非常简单的事情,当然这全都仰仗freeswitch对media bug的封装。
我们直接定位源码,文件 switch_core_media_bug.c 来仔细看看freeswitch都为我们带来了哪些方法和实现。
switch_core_media_bug_get_session 非常重要
switch_core_media_bug_get_text
switch_core_media_bug_get_video_ping_frame
switch_core_media_bug_get_write_replace_frame 获取媒体frame
switch_core_media_bug_set_write_replace_frame 写入
switch_core_media_bug_add 添加media bug
switch_core_media_bug_close
switch_core_media_bug_remove
这些方法都非常重要, 我们可以根据以上方法在freeswitch源码中查到对应的调用,那么自己写起来就可以模仿(和我一样不熟悉c语音的同学),那么我们一样能写出可以实现自身意图的功能。
我们再一次总结一下,freeswitch的media bug,首先我们今天先介绍了什么是freeswitch的media bug
1:什么是media bug
2:media bug 在freeswitch中有哪些应该,我们在哪些场景可以采纳
3:media bug在源码中的使用,包括eavesdrop 中的使用
4:浓缩丸media bug的callback代码,我们在实践中需要如何处理
5:查询switch_core_media_bug.c 中定义了哪些非常重要的方法
今天的回顾结束,下一节,我们将结束如何将media bug中的媒体进行转发处理,对接ASR。
在这之前我们选择使用UDP服务将media实时传到我们熟悉的应用开发语言上。