FreeSWITCH实现在视频通话中某一方视频翻转
客户项目使用浏览器+webrtc+FreeSWITCH在各类国产化终端间(windows+kylin+android+emss等)实现音视频通信、状态呈现以及即时消息。本来实施挺顺利,但客户新引进了一批新FT终端,摄像头画面竟然向左翻转了90度,关键是,客户认为终端质量没有问题,让软件系统自行解决。
翻遍了v4l2驱动配置和webrtc js说明都找不到相关办法,虽然浏览器中能让摄像头显示时翻转回来,但webrtc编码后仍旧是原来的样子,通信的对端看视频的时候别扭极了。。。
无奈,咱只对服务端的开发比较熟悉,只能在FreeSWITCH上修改了。
其实FreeSWITCH中内置了对视频翻转的基本方法,只是没有实现对特定通道的视频翻转,请看代码:
typedef enum {
SRM_NONE = 0, // No rotation.
SRM_90 = 90, // Rotate 90 degrees clockwise.
SRM_180 = 180, // Rotate 180 degrees.
SRM_270 = 270, // Rotate 270 degrees clockwise.
} switch_image_rotation_mode_t;
SWITCH_DECLARE(void) switch_img_rotate(switch_image_t **img, switch_image_rotation_mode_t mode);
SWITCH_DECLARE(void) switch_img_rotate_copy(switch_image_t *img, switch_image_t **new_img, switch_image_rotation_mode_t mode);
会议模块也实现了对会议中特定成员的翻转、镜像控制:
if (conference_utils_member_test_flag(member, MFLAG_FLIP_VIDEO) ||
conference_utils_member_test_flag(member, MFLAG_ROTATE_VIDEO) || conference_utils_member_test_flag(member, MFLAG_MIRROR_VIDEO)) {
if (conference_utils_member_test_flag(member, MFLAG_ROTATE_VIDEO)) {
if (member->flip_count++ > (int)(member->conference->video_fps.fps / 2)) {
member->flip += 90;
if (member->flip > 270) {
member->flip = 0;
}
member->flip_count = 0;
}
switch_img_rotate_copy(frame->img, &img_copy, member->flip);
} else if (conference_utils_member_test_flag(member, MFLAG_MIRROR_VIDEO)) {
switch_img_mirror(frame->img, &img_copy);
} else {
switch_img_rotate_copy(frame->img, &img_copy, member->flip);
}
}
有了这个基础,我们就可以针对特定的终端进行翻转控制了:
1、修改用户配置
以用户1000为例,修改conf/directory/default/1000.xml,增加如下行,表示我们要对该用户发出的视频向右翻转90度:
<variable name="read_video_rotate" value="90"/>
2、在core session解析codec settings时增加下述代码:
if ((rotate = switch_channel_get_variable(session->channel, "read_video_rotate"))) {
switch_channel_set_flag_recursive(session->channel, CF_VIDEO_DECODED_READ);
engine->codec_settings.video.rotate = atoi(rotate);
}
CF_VIDEO_DECODED_READ是个什么鬼呢?它的用途其实是,对用户的视频强制解码,为啥要解码?为了翻转呀,不解码怎么翻转。
3、执行翻转,在avcodec中增加下述代码:
if(context->codec_settings.video.rotate > 0){
switch_img_rotate(&(context->img),(switch_image_rotation_mode_t)context->codec_settings.video.rotate);
}
上述代码的逻辑,应该是无懈可击的,但有一个小小的遗憾,因为要解码、翻转,需要将FreeSWITCH置为全媒体模式,即在sip internal中将inbound-bypass-meida inbound-proxy-mdia关闭
<!--Uncomment to set all inbound calls to no media mode-->
<!-- <param name="inbound-bypass-media" value="true"/> -->
<!--Uncomment to set all inbound calls to proxy media mode-->
<!-- <param name="inbound-proxy-media" value="true"/> -->
但这样的设置给FreeSWITCH带来了极大的性能风险,因为这样一来,所有媒体都要经过FreeSWITCH,而且媒体还可能重新编解码。
有没有更好的方式解决这个问题呢,答案是有,我们可以关闭全媒体模式,在dialplan中仅针对这一批终端打开全媒体模式,首先在你的用户配置lua脚本(或写一个新的lua脚本)中增加下述代码:
session:setAutoHangup(false);
local read_video_rotate = session:getVariable("read_video_rotate");
if(read_video_rotate ~= nil) then
session:execute("media_reset");
end
然后在dialplan中引用此脚本:
<action application="lua" data="xxxx.lua"/>
好了,至此问题完美解决,仅当在特定用户配置中设置了read_video_rotate的用户,FreeSWITCH才将其呼叫设置为全媒体模式,进行解码、翻转、编码并发给对端,兼顾了FreeSWITCH的性能和功能。