uuid_media_reneg接口实现流程
uuid_media_reneg接口实现流程
一、实现位置
uuid_media_reneg api接口位于mod_command模块,函数位置定义在mod_command.c大约3741行左右。原型如下:
SWITCH_STANDARD_API(uuid_media_neg_function) { // to do }
在mod_commands_load函数中注册uuid_media_reneg api接口。位置大约在mod_command.c大约7710行左右。注册方式如下:
SWITCH_ADD_API(commands_api_interface, "uuid_media_reneg", "Media negotiation", uuid_media_neg_function, MEDIA_RENEG_SYNTAX);
同时也在mod_commands_load实现了控制台自动填充uuid功能,位置大约在mod_command.c大约7717行左右:
switch_console_set_complete("add uuid_media_reneg ::console::list_uuid");
二、内部实现流程
uuid_media_neg_function内部实现如下:
#define MEDIA_RENEG_SYNTAX "<uuid>[ <codec_string>]" SWITCH_STANDARD_API(uuid_media_neg_function) { char *mycmd = NULL, *argv[2] = { 0 }; int argc = 0; switch_status_t status = SWITCH_STATUS_FALSE; if (!zstr(cmd) && (mycmd = strdup(cmd))) { argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); } if (zstr(cmd) || argc < 1 || zstr(argv[0])) { stream->write_function(stream, "-USAGE: %s\n", MEDIA_RENEG_SYNTAX); } else { switch_core_session_message_t msg = { 0 }; switch_core_session_t *lsession = NULL; char *uuid = argv[0]; msg.message_id = SWITCH_MESSAGE_INDICATE_MEDIA_RENEG; msg.string_arg = argv[1]; msg.from = __FILE__; if (*uuid == '+') { msg.numeric_arg++; uuid++; } if ((lsession = switch_core_session_locate(uuid))) { status = switch_core_session_receive_message(lsession, &msg); switch_core_session_rwunlock(lsession); } } if (status == SWITCH_STATUS_SUCCESS) { stream->write_function(stream, "+OK Success\n"); } else { stream->write_function(stream, "-ERR Operation Failed\n"); } switch_safe_free(mycmd); return SWITCH_STATUS_SUCCESS; }
其中SWITCH_STANDARD_API为一个宏,原型如下:
#define SWITCH_STANDARD_API(name) static switch_status_t name ( _In_opt_z_ const char *cmd, _In_opt_ switch_core_session_t *session, _In_ switch_stream_handle_t *stream )
说明FreeSWITCH回调uuid_media_neg_function函数接口时,会传三个参数:
- cmd:音视频编解码格式
- session:本函数没有使用该参数,通话session,而是通过uuid去查找通话session。
- stream:可以理解数据回写流,比如函数执行状态(字符串提示信息)回传给调用方或控制台回显等。
进入函数后,首先调用zstr()判断cmd参数是否有值。
zstr(cmd)
重新分配内存,将cmd参数值保存到新分配的内存,且将该内存控制权交给mycmd指针:
mycmd = strdup(cmd)
如果cmd参数不为空,且参数复制成功后,调用switch_separate_string解析cmd传入的参数,同时返回参数数量,argv为长度为2的字符串指针数组,argv[0]指向uuid字符串存放内存,argv[1]指向音视频编码格式存放内存。
argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0])));
接下来判断参数是否合法,如果cmd没有值,或者cmd传进字符串数量小于1,则回显操作提示信息:
stream->write_function(stream, "-USAGE: %s\n", MEDIA_RENEG_SYNTAX);
如果参数合法,则创建session消息体:
switch_core_session_message_t msg = { 0 };
初始化消息id,并将音视频编码格式传赋值给消息参数成员:
msg.message_id = SWITCH_MESSAGE_INDICATE_MEDIA_RENEG; msg.string_arg = argv[1];
调用switch_core_session_locate函数通过uuid查找通话的session:
switch_core_session_locate(uuid)
如果session存在,则调用switch_core_session_receive_message发起re-invite进行媒体协商。
调用switch_core_session_receive_message发起消息后,需要使用switch_core_session_rwunlock释放锁。
switch_core_session_rwunlock(lsession);
判断操作是否成功,回写函数执行状态信息:
if (status == SWITCH_STATUS_SUCCESS) { stream->write_function(stream, "+OK Success\n"); } else { stream->write_function(stream, "-ERR Operation Failed\n"); }
调用switch_safe_free()释放mycmd指向的内存。
switch_safe_free(mycmd);
最后返回函数执行状态。
三、switch_core_session_receive_message函数解析
switch_core_session_receive_message其实是一个宏,定义在switch_core.h文件中,原型如下:
#define switch_core_session_receive_message(_session, _message) \ switch_core_session_perform_receive_message(_session, _message, \
__FILE__, __SWITCH_FUNC__, __LINE__)
switch_core_session_perform_receive_message()函数在switch_core_session.c进行实现。在此函数体中有许多执行分支,我们只看与消息id为SWITCH_MESSAGE_INDICATE_MEDIA_RENEG相关的执行路径。所在可以直接跳到switch_core_session.c文件920行,判断channel是否存在,如果不存在则打印消息,提示channel is hungup already。如果channel存在则调用switch_core_media_receive_message函数。
if (switch_channel_down_nosig(session->channel)) { switch_log_printf(SWITCH_CHANNEL_ID_LOG, message->_file, message->_func, message->_line, switch_core_session_get_uuid(session), SWITCH_LOG_DEBUG, "%s skip receive message [%s] (channel is hungup already)\n", switch_channel_get_name(session->channel), message_names[message->message_id]); } else { if (session->media_handle) { status = switch_core_media_receive_message(session, message); } if (status == SWITCH_STATUS_SUCCESS) { if (session->endpoint_interface->io_routines->receive_message) { status = session->endpoint_interface->io_routines->receive_message(session, message); } } }
可以看到在这里,调用了switch_core_media_receive_message()函数,并在这个函数中更新channel中相关音视频编码格式,同时更新sdp信息:
case SWITCH_MESSAGE_INDICATE_MEDIA_RENEG: { switch_core_session_t *nsession; if (msg->string_arg) { switch_channel_set_variable(session->channel, "absolute_codec_string", NULL); if (*msg->string_arg == '=') { switch_channel_set_variable(session->channel, "codec_string", msg->string_arg); } else { switch_channel_set_variable_printf(session->channel, "codec_string", "=%s", switch_channel_get_variable(session->channel, "ep_codec_string")); } a_engine->codec_negotiated = 0; v_engine->codec_negotiated = 0; smh->num_negotiated_codecs = 0; switch_channel_clear_flag(session->channel, CF_VIDEO_POSSIBLE); switch_core_media_prepare_codecs(session, SWITCH_TRUE); switch_core_media_check_video_codecs(session); switch_core_media_gen_local_sdp(session, SDP_TYPE_REQUEST, NULL, 0, NULL, 1); } if (msg->numeric_arg && switch_core_session_get_partner(session, &nsession) == SWITCH_STATUS_SUCCESS) { msg->numeric_arg = 0; switch_core_session_receive_message(nsession, msg); switch_core_session_rwunlock(nsession); } } break;