OMCS 开发手册(02) -- 多媒体连接器
多媒体设备管理器 一文,我们从Owner的角度详细描述了多媒体设备管理器的使用,本文我们将站在Guest的角度,描述OMCS中另一类组件/控件:多媒体连接器。多媒体连接器用于连接到任何一个在线的OMCS客户端所提供的多媒体设备。所有的连接器都是以Windows控件或组件的方式呈现的,且都实现了IMultimediaConnector接口。
当多媒体连接器成功连接到目标设备后,连接器便可以从设备获取其采集到的数据并进行播放。比如,CameraConnector连接到目标摄像头后,便会显示摄像头采集得到的视频。
要特别注意,连接器和设备之间的连接是一个“虚拟连接”,它以OMCS客户端与服务器之间的真实TCP连接为基础。在OMCS环境中,所谓的连接器与目标设备之间连接成功,意味着是这个“虚拟连接”创建完成;所谓连接器与目标设备断开,指的是这个“虚拟连接”的释放。显然,虚拟连接的释放并不会导致底层真实的TCP连接断开。但是,反过来,只要底层的TCP连接断开,就会导致基于该TCP连接的所有“虚拟连接”都被释放。
一. IMultimediaConnector 接口
多媒体连接器的接口定义如下所示:
public interface IMultimediaConnector { /// <summary> /// 设备主人的UserID。 /// </summary> string OwnerID { get; } /// <summary> /// 与目标设备是否已连接? /// </summary> bool Connected { get; } /// <summary> /// 连接器是否已经被释放? /// </summary> bool IsDisposed { get; } /// <summary> /// 在因为自己掉线或Owner掉线而导致当前连接器断开时,是否开启自动重连的功能。默认值为false。 /// </summary> bool AutoReconnect { get; set; } /// <summary> /// 目标多媒体设备的类型。 /// </summary> MultimediaDeviceType MultimediaDeviceType { get; } /// <summary> /// 当连接目标多媒体设备的尝试(由BeginConnect发起)结束时,触发此事件。事件参数说明了连接的结果。 /// </summary> event CbGeneric<string, ConnectResult> ConnectEnded; /// <summary> /// 当与目标多媒体设备的连接断开时,触发该事件。 /// </summary> event CbGeneric<string, ConnectorDisconnectedType> Disconnected; /// <summary> /// 在AutoReconnect为true的情况下,当开始自动重连目标多媒体设备时,触发该事件。 /// </summary> event CbGeneric<string> AutoReconnectStart; /// <summary> /// 在AutoReconnect为true的情况下,当自动重连目标多媒体设备成功时,触发该事件。 /// </summary> event CbGeneric<string> AutoReconnectSucceed; /// <summary> /// 在AutoReconnect为true的情况下,当自动重连目标多媒体设备失败时,触发该事件。事件参数说明了连接失败的原因。 /// </summary> event CbGeneric<string, ConnectResult> AutoReconnectFailed; /// <summary> /// 当一个进程中有多个IMultimediaManager实例时,调用该方法来指定当前连接器究竟要使用哪一个。该方法必须在BeginConnect方法之前调用。 /// </summary> void SetMultimediaManager(IMultimediaManager mgr); /// <summary> /// 尝试连接目标多媒体设备。如果多媒体设备未被授权、或多媒体管理器未成功初始化、或当前连接器正在工作、或目标多媒体设备已经被连接、或上次的连接尝试还未结束,则将抛出相应的异常。参数:destUserID:目标用户的UserID /// </summary> void BeginConnect(string destUserID); /// <summary> /// 与目标用户的多媒体设备断开连接,并释放通道。 /// </summary> void Disconnect(); /// <summary> /// 获取通道质量(从Owner到当前Guest的通道传输质量)。 取值:0~10。值越大,表示通道质量越好。 /// </summary> int GetChannelQuality(); }
1.连接
当使用连接器对象时,通常首先是将对应的控件/组件拖到窗体上,然后调用其BeginConnect方法尝试与目标用户的多媒体设备进行连接。连接结束时,无论是成功还是失败,都会触发ConnectEnded事件。我们可以根据ConnectEnded事件参数ConnectResult得知本次连接是成功还是失败。ConnectResult枚举定义如下:
public enum ConnectResult { Succeed, /// <summary> /// 等待回复超时 /// </summary> Timeout, /// <summary> /// 目标用户不在线 /// </summary> TargetUserOffline, /// <summary> /// 设备不存在或出错 /// </summary> DeviceInvalid , /// <summary> /// Owner的设备管理器还未完成初始化 /// </summary> MultimediaManagerNotInitialized, /// <summary> /// 出现异常 /// </summary> ExceptionOccured, /// <summary> /// 自己已掉线 /// </summary> SelfOffline, /// <summary> /// 通道不可用(当前客户端与OMCS之间的TCP连接断开了) /// </summary> ChannelUnavailable, /// <summary> /// 不能重复连接同一用户的同一设备 /// </summary> CantConnectRepeatly, /// <summary> /// 本次OMCS授权不包含目标设备类型 /// </summary> DeviceUnauthorized, /// <summary> /// 设备已经被其它程序占用 /// </summary> DeviceInUsing }
注意,如果多媒体设备未被授权、或多媒体管理器未成功初始化、或当前连接器正在工作、或目标多媒体设备已经被连接,则BeginConnect方法将抛出相应的异常。
如果要连接的目标设备的Owner还未上线,WaitOwnerOnlineSpanInSecs属性则允许我们等它一段时间。在某些情况下,这可能是很有用的。我们来设想一下,通常基于OMCS开发的多媒体应用系统,除了有OMCS提供多媒体的部分,还会有其它业务逻辑,也就是说,除了有OMCS服务器存在,还会有处理业务逻辑的应用服务器存在。就像概述中的那个图所示的情况:
此时,客户端将有两个连接,一个连接指向OMCS服务器,另一个连接指向应用服务器。通常,客户端应该在成功登录了应用服务器之后,才会去连接OMCS服务器,这样就可能存在一个时间间隙 -- 即应用服务器已经连接成功,而OMCS服务器还未连接。如果在这个时候,其它Guest要访问当前客户端的多媒体设备,就会返回TargetUserOffline的结果而连接失败。如果将连接器的WaitOwnerOnlineSpanInSecs设置大于0,则连接器会在这段时间内不断轮询,等待Owner连上OMCS服务器。当Owner上线的时候,再去连接其多媒体设备。这样就解决了问题。当然,如果由于某些意外,导致Owner在WaitOwnerOnlineSpanInSecs时间内都还未连上OMCS服务器,则在等待时间结束时,连接器仍然返回TargetUserOffline的结果。
2.状态信息
MultimediaDeviceType 属性表示当前连接器要连接的目标多媒体设备的类型。
Connected 属性反应了当前连接器与多媒体设备之间的连接状态。
IsDisposed 属性表明当前连接器实例是否已经被释放(其Dispose方法已经被调用)。如果 IsDisposed为true,表明后续都不能再使用该连接器实例了。
如果Connected为true,则OwnerID属性表示当前连接的是哪个用户的多媒体设备。
3.断开连接
我们可以调用Disconnect方法主动断开与目标多媒体设备的连接。当然,除了主动断开连接外,还有其它几种方式也会导致连接器到目标设备的连接断开(比如,网络断开)。而只要连接器与目标多媒体设备之间的连接断开,就会触发Disconnected事件,事件的参数ConnectorDisconnectedType说明了连接断开的原因。
public enum ConnectorDisconnectedType { /// <summary> /// Guest(连接器)掉线。 /// </summary> GuestOffline = 0, /// <summary> /// Owner(设备)掉线。 /// </summary> OwnerOffline, /// <summary> /// Guest(连接器)主动断开到设备的连接。 /// </summary> GuestActiveDisconnect, /// <summary> /// Owner(设备)主动断开Guest(连接器)到设备连接。 /// </summary> OwnerActiveDisconnect, /// <summary> /// 试用超时。 /// </summary> TrialTimeout }
多媒体连接器断开共有五种原因:Guest掉线、Owner掉线、Guest主动断开、Owner主动断开、试用超时(对于免费版本的OMCS而言)。
当我们在正常工作的连接器实例上调用其Disconnect方法时,触发Disconnected事件的参数就是ConnectorDisconnectedType.GuestActiveDisconnect。
还记得在介绍多媒体设备管理器 时,IMultimediaManager有重载的DisconnectGuest方法,如果Owner调用这个DisconnectGuest方法,那么在Guest这一方对应的连接器实例就会断开到目标设备的连接,而断开的原因就正是ConnectorDisconnectedType.OwnerActiveDisconnect。
二.四种多媒体连接器
OMCS提供了四种多媒体连接器:MicrophoneConnector(麦克风连接器)、CameraConnector/DynamicCameraConnector(摄像头连接器)、DesktopConnector/DynamicDesktopConnector(远程桌面连接器)、WhiteBoardConnector(电子白板连接器)。所有这些连接器都实现了IMultimediaConnector接口,所以,IMultimediaConnector定义的功能它们都是拥有的。
我们可以将这些连接器组件/控件添加到工具箱中:在VS的工具箱的空白地方右键快捷菜单 => 选择项,在弹出的“选择工具箱项”的窗体上,点击“浏览”按钮,选中OMCS.dll文件,再点击“确定”就可以了。
1.麦克风连接器
MicrophoneConnector是一个组件,没有UI元素,当然,它也不需要UI显示。其提供的相关API如下所示:
/// <summary> /// 当Owner音频输出控制改变时,触发此事件。【对应于Owner端的多媒体管理器的OutputAudio属性的修改】 /// </summary> event CbGeneric OwnerOutputChanged; /// <summary> /// 当接收到目标用户的麦克风的数据时,触发该事件。(解码后的数据,20ms) /// </summary> event CbGeneric<byte[]> AudioDataReceived; /// <summary> /// 当Mute属性设置为true时,不会播放接收到的数据,但是,此时需要触发AudioDataReceived事件吗?默认值为false。 /// </summary> bool SpringReceivedEventWhenMute { get; set; } /// <summary> /// 是否静音。默认值false。注意,即使设置为true,也不影响AudioDataReceived事件的触发。 /// </summary> bool Mute { get; set; } /// <summary> /// Owner是否输出了音频。【对应于Owner端的多媒体管理器的OutputAudio属性】 /// </summary> bool OwnerOutput { get; }
比如,我们如果希望将麦克风连接器播放的声音录制下来,那么我们可以通过AudioDataReceived事件拿到声音数据。
2.视频连接器
OMCS提供了一个视频连接器:VideoConnector,作为摄像头连接器和桌面连接器的基类使用。
/// <summary> /// 获取自连接成功后接收到的视频帧的总数。 /// </summary> ulong GetReceivedFrameCount(); /// <summary> /// 获取实时的视频参数。 /// </summary> VideoParameters GetRTVideoParameters(); /// <summary> /// 是否在视频图像上面打印视频的相关信息(视频尺寸、编码质量、帧频)。默认值:false。 /// </summary> bool DisplayVideoParameters { get; set; } /// <summary> /// 是否启用仅监听模式。默认值:false。 如果为true,表示只接收数据,而不解码和绘制图像。也即:会触发FrameEncodedReceived事件,但是不会触发NewFrameReceived事件。 /// 注意:必须在BeginConnect之前设置该属性。 /// </summary> bool JustListen { get; set; } /// <summary> /// 目标多媒体设备的类型。 /// </summary> MultimediaDeviceType MultimediaDeviceType { get; } /// <summary> /// Owner端的机器类型。初始值为Unknown,连接对方摄像头成功后,才会返回正确的机器类型。 /// </summary> MachineType OwnerMachineType { get; } /// <summary> /// 当接收到目标用户的一帧视频数据(尚未解码)时,触发该事件。参数:OwnerID - data(编码数据)。 /// </summary> event CbGeneric<string, byte[]> NewFrameEncodedReceived; /// <summary> /// 当接收到来自Owner的新的一帧图像时,触发此事件。参数:OwnerID - data(不包含头信息,RGBA32数据)。 /// </summary> event CbGeneric<string, byte[]> NewFrameReceived; /// <summary> /// 当检测到Owner采集的视频大小发生变化时,触发此事件。参数:OwnerID - 新的视频大小。 /// </summary> event CbGeneric<string, Size> OwnerVideoSizeChanged;
2.摄像头连接器
OMCS提供了两个摄像头连接器:CameraConnector和DynamicCameraConnector。它们的区别在于,CameraConnector是一个UI控件,直接在当前的UI上显示目标摄像头采集到的视频;DynamicCameraConnector是一个组件(没有UI),可以通过SetViewer方法动态地为其设置要在哪个UI上绘制。可以看作 CameraConnector 是对DynamicCameraConnector的一个简单封装,相比而言,DynamicCameraConnector暴露的API 比 CameraConnector更多一些。
/// <summary> /// 设置要显示视频的控件。必须要在UI线程中调用该方法。 /// </summary> /// <param name="newPanel">要绘制视频的控件。可以为null。</param> void SetViewer(Control newPanel); /// <summary> /// 修改Owner的视频编码质量。 /// </summary> /// <param name="quality">编码质量。取值0~31,值越小,越清晰。</param> void ChangeOwnerCameraEncodeQuality(int quality); /// <summary> /// 修改Owner的摄像头的采集分辨率。如果其摄像头不支持该分辨率,则将切换到最接近指定值的分辨率上。如果Owner方修改成功,将会触发本地的OwnerCameraVideoSizeChanged事件。 /// </summary> /// <param name="widthAddHeight">新的采集分辨率的宽和高之和。比如分辨率为(320*240),则该参数为320+240=560</param> void ChangeOwnerCameraVideoSize(int widthAddHeight); /// <summary> /// 修改Owner的摄像头的采集分辨率。如果其摄像头不支持该分辨率,则将切换到最接近指定值的分辨率上。如果Owner方修改成功,将会触发本地的OwnerCameraVideoSizeChanged事件。 /// </summary> /// <param name="newSize">新的采集分辨率。</param> void ChangeOwnerCameraVideoSize(Size newSize); /// <summary> /// 修改Owner的摄像头的输出控制。如果Owner方修改成功,将会触发本地的OwnerOutputChanged事件。 /// </summary> /// <param name="output">是否输出视频。</param> void ChangeOwnerOutput(bool output);
像我们经常看到的视频聊天中的全屏显示功能,就可以采用DynamicCameraConnector实现,当用户点击全屏按钮时,将DynamicCameraConnector要绘制的表面设置为最前(Top)的窗体的表面就可以了。
注意:如果使用DynamicCameraConnector在自定义的控件或窗体表面绘制视频,那么,为了防止闪烁,需要您在代码中设置双缓冲。在控件或窗体的构造函数中增加以下代码:
public MyControl() { InitializeComponent();
this.SetStyle(ControlStyles.ResizeRedraw, true);//调整大小时重绘 this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);// 双缓冲 this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);// 禁止擦除背景. this.SetStyle(ControlStyles.UserPaint, true);//自行绘制 this.UpdateStyles(); }
除此之外,CameraConnector和DynamicCameraConnector还提供了以下特性:
/// <summary> /// 当检测到Owner的摄像头采集的视频大小发生变化时,触发此事件。参数为新的视频大小。 /// </summary> event CbGeneric<Size> OwnerCameraVideoSizeChanged; /// <summary> /// 当Owner视频输出控制改变时,触发此事件。【对应于Owner端的多媒体管理器的OutputVideo属性的修改】 /// </summary> event CbGeneric OwnerOutputChanged; /// <summary> /// Owner是否输出了视频。【对应于Owner端的多媒体管理器的OutputVideo属性】 /// </summary> bool OwnerOutput { get; } /// <summary> /// 视频的尺寸(长和宽都已经修正为8的整数倍)。 /// </summary> Size VideoSize { get; } /// <summary> /// 获取当前正在绘制的图像。 /// </summary> Bitmap GetCurrentImage(); /// <summary> /// 获取当前视频的编码质量。0~31,数值越小越清晰。 /// </summary> int GetVideoQuality(); /// <summary> /// 视频图像绘制模式 /// </summary> VideoDrawMode VideoDrawMode { get; set; }
GetCurrentImage:该方法可以将当前显示的视频帧保存为位图。使用该方法可以实现拍照功能。
AutoSynchronizeVideoToAudio:如果当前客户端连接了同一个Owner的摄像头和话筒,那么,CameraConnector/DynamicCameraConnector在播放视频时是否自动与音频保持同步。
VideoDrawMode:该属性用于设定视频图像绘制模式。
一般,在网络非常顺畅的情况下,视频帧与音频帧按是照接收就立即播放的模式来进行的,这本来就是同步的。但是,当网络存在抖动时,OMCS内部会自动启用抖动缓冲区(Jitter Buffer),这样就使得音频比视频的播放要稍慢一点(可能是几毫秒或几十毫秒,取决于网络抖动的幅度),而导致出现声音与画面不同步的情况。如果将AutoSynchronizeVideoToAudio设置为true,则OMCS会控制视频帧的播放,使其与音频始终保持一致。
3.远程桌面连接器
同摄像头连接器一样,OMCS也提供了两种远程桌面连接器:DesktopConnector和DynamicDesktopConnector。它们的区别也与两种摄像头连接器的区别一样,同理,使用DynamicDesktopConnector也需要设置双缓冲绘制,同DynamicCameraConnector一样。
远程桌面连接器的扩展特性如下所示:
/// <summary> /// 当检测到Owner的屏幕分辨率发生变化时,触发此事件。参数为新的分辨率。 /// </summary> event CbGeneric<Size> OwnerDesktopSizeChanged; /// <summary> /// 当Owner桌面图像输出控制改变时,触发此事件。【对应于Owner端的多媒体管理器的OutputDesktop属性的修改】 /// </summary> event CbGeneric OwnerOutputChanged; /// <summary> /// 当Owner允许guest操作桌面的授权改变时,触发此事件。【对应于Owner端的多媒体管理器的AllowControl属性的修改】 /// </summary> event CbGeneric OwnerAllowControlChanged; /// <summary> /// Owner是否输出了桌面图像。【对应于Owner端的多媒体管理器的OutputDesktop属性】 /// </summary> bool OwnerOutput { get; } /// <summary> /// 是否仅仅允许查看远程桌面,但是不能进行操作。 /// </summary> bool WatchingOnly { get; set; } /// <summary> /// 是否在远程桌面上显示Owner的鼠标光标。默认值为true。 /// 注意:前提是WatchingOnly为true时,该属性设置才有效。 /// </summary> bool ShowMouseCursor { get; set; }
/// <summary> /// Owner是否允许操作桌面。【对应于Owner端的多媒体管理器的AllowControl属性】。注意其与WatchingOnly的区别:只有OwnerAllowControl为true的前提下,WatchingOnly的属性设置才有效果。 /// </summary> bool OwnerAllowControl { get; } /// <summary> /// 远程桌面的尺寸(长和宽都已经修正为8的整数倍)。 /// </summary> Size DesktopSize { get; } /// <summary> /// 是否将屏幕图像自动拉伸成Viewer控件的大小。 /// </summary> bool AdaptiveToViewerSize { get; } /// <summary> /// 获取当前正在绘制的图像。 /// </summary> Bitmap GetCurrentImage(); /// <summary> /// 获取当前视频的编码质量。0~31,数值越小越清晰。 /// </summary> int GetVideoQuality();
WatchingOnly 属性用于控制guest是否可以操作远程桌面。将其设为false,就可以实现类似QQ的远程协助的功能(当然前提是OwnerAllowControl 为true)。(特别提醒:如果将WatchingOnly设置为了false,仍然不能操作远程桌面,则查看远程电脑是否启动了360安全卫士,可以退出360后再次尝试。)
ShowMouseCursor 属性用于控制是否在远程桌面上显示Owner的鼠标光标。比如,在远程教学系统中,可以将该属性设置为true,这样,每个guest都可以看到Owner鼠标指示的地方了。
4.电子白板连接器
OMCS的电子白板主要提供了以下功能:
(1)电子白板提供了常用的视图元素:像直线、曲线、箭头、矩形、三角形、椭圆、文字等;且支持视图元素的上下对齐,左右对齐。
(2)可修改边框颜色、填充颜色、线条粗细、线条虚实、显示比例。
(3)可插入图片、截屏,并可将整个白板保存为位图。
(4)支持课件:上传课件、打开课件、删除课件,课件翻页等。且这些操作会自动同步到连接到了同一白板的各个客户端。
(5)白板分页:在不使用课件的情况下,可以进行新建页、删除页、翻页等操作。
(6)激光笔:OMCS会将老师/主讲人的激光笔位置自动同步到各个客户端。
电子白板的工具栏中各工具的说明如下图所示:
首先要强调一点,电子白板这个设备与其它的几个设备有个重要的区别:Owner的身份对于电子白板而言,更像是一个标志,而不是像前面三种设备一样,是实际设备的持有者。就像一栋大楼一样,里面有很多个房间,WhiteBoardConnector的BeginConnect方法的参数destUserID的含义不再是用户帐号,而是代表房间的门牌号码。如果多个guest连到了同一个Owner的电子白板,意味着多个guest进入了同一个房间,可以在同一个电子白板上相互协作。这些guest看到的是完全相同的内容,当一个guest修改电子白板的内容时,其它的guest可以同时看到这种改变。
基于此,电子白板连接器在连接Owner时,Owner不需要在线;而且,Owner的掉线也不会导致Guest的电子白板连接器断开,也就是说,WhiteBoardConnector的Disconnected事件的ConnectorDisconnectedType参数的值永远不会是OwnerOffline。
电子白板连接器WhiteBoardConnector的扩展特性如下:
/// <summary> /// 初始加载白板内容时的超时,单位:秒。默认值为120秒。 /// </summary> int Timeout4LoadContent { get; set; } /// <summary> /// 是否开启自动重连的功能。默认值为true。 /// </summary> bool AutoReconnect { get; set; } /// <summary> /// 仅仅允许查看白板,但是不能进行操作。默认值为false。 /// </summary> bool WatchingOnly { get; set; } /// <summary> /// 白板页的背景图片(比如,可以将课件转换成图片后,设置到该属性上)。 /// </summary> Image BackImageOfPage { get; set; } /// <summary> /// 白板页的尺寸。 /// </summary> Size PageSize { get; set; } /// <summary> /// 右键菜单文字是否使用英语。 /// </summary> bool ContextMenuEnglish { get; set; } /// <summary> /// 如果协作者新建了一个view,则自动选中该view,并调节滚动条使其在可视区域内。 /// </summary> bool FocusOnNewViewByOther { get; set; } /// <summary> /// 是否显示白板页的边框。默认值:false。 /// </summary> bool DisplayPageBorder { get; set; } /// <summary> /// 是否为管理员身份。管理员的特殊权限:上传课件、打开课件、删除课件、翻页等。 /// </summary> bool IsManager { get; set; }
AutoReconnect 是电子白板连接器特有的一个功能,以支持断线自动重连。当断线重连成功后,电子白板会从服务器下载最新的内容,并显示,以保证电子白板的实时性。
WatchingOnly 属性用于控制guest是否可以在电子白板上绘图等操作,还是只能观看。在现在流行的电子课堂中,通常只有老师可以操作电子白板,而学生只能观看电子白板。
FocusOnNewViewByOther 属性如果设置为true,当老师画了一个三角形,则学生的白板控件的滚动条会自动滚动到合适的位置以将新画的三角形显示出来。
IsManager 属性如果为true,则表示当前用户可以进行上传课件、打开课件、删除课件、课件翻页等操作。所以,IsManager通常用于区别像老师和学生这样的角色。
关于在实际项目中使用WhiteBoardConnector的更多经验介绍,可以参考下面两篇文章:
OMCS使用技巧 -- 扩展电子白板支持课件的类型:word、pdf、ppt
--------------------------------------------------------------------------------------------------------------------
阅读 更多OMCS开发手册系列文章 。
Q Q:168757008
官网: www.oraycn.com