PhoneCall是封装了一个电话的状态和行为的模型类。是一个电话的核心
PhoneCall的核心功能有两个,一个是记录电话状态,并提供一个观察者提供状态变化时的信号stateChangedEvent(用此实现观察者模式)。
另一个是通过调用IPhoneLine的接口实现accept(),resume(),hold(),close()四个主动的动作。
一、主动的动作
先看四个动作的实现,为方便起见,用P代表视图,C代表控制,M表示模型。前后表示执行前后实例在哪个部分内。注意上下文的切换。
这4个函数的调用链如下:
resume:
(P) QtPhoneCall::_actionHold QT的QAction connect 到 QtPhoneCall::holdOrResume (P)
(P) QtPhoneCall::holdOrResume 调用 CPhoneCall::resume或者CPhoneCall::hold (C)
(C) CPhoneCall::resume PostEvent到线程 QuteCom实例,实例线程将执行CPhoneCall::resumeThreadSafe() 原线程(QtApplication)返回(C)
(C) 切换上下文至qutecom线程执行,CPhoneCall::resumeThreadSafe()调用PhoneCall::resume (M)
(M) PhoneCall::resume调用PhoneLine::resumeCall(_callId) (M) 注意这里程序逻辑跳到PhoneLine内
(M) PhoneCall ----> PhoneLine (M)
(M) PhoneLine::resumeCall 调用 SipWrapper::resumeCall
至此跳出owPhone模块到SipWrapper(PhApiWrapper)内。
hold:
与resume相同,在QtPhoneCall::holdOrResume处分支。QtPhoneCall::holdOrResume视情况调用CPhoneCall::resume或者CPhoneCall::hold
accept:
(P) QtPhoneCall按钮accept或者QtoolBar按钮accept或者QtCallToaster::pickUpButtonClicked或者callButtonClicked(条件ActiveCall)
(C) CPhoneCall::accept PostEvent至QuteCom实例线程执行acceptThreadSafe (C)
(C) 切换上下文,acceptThreadSafe调用PhoneCall::accept (M)
(M) PhoneCall::accept 调用 PhoneLine::acceptCall(_callId); (M) 注意这里程序逻辑跳到PhoneLine内
(M) PhoneCall ----> PhoneLine (M)
(M) PhoneLine::acceptCall调用SipWrapper::acceptCall
至此跳出owPhone模块到SipWrapper(PhApiWrapper)内。
close:
(P) QtPhoneCall:: _ui::rejectButton clicked() | _actionHangupCall::trigled() | _callToaster::hangUpButtonClicked()
(P) QtPhoneCall::rejectCall() --- CPhoneCall.hangUp (C)
(C) CPhoneCall.hangUp --- postEvent(hangUpThreadSafe)
(C) switch context -- hangUpThreadSafe
(M) PhoneCall.close()
(M) _phoneLine.rejectCall(_callId) | _phoneLine.closeCall(_callId)
(SIPWrapper) sipWrapper::rejectCall | sipWrapper::closeCall
End owphone
另外,在状态变化后的applyState中视条件调用hold和resume
注意PhoneCall没有makeCall这个函数,因为从逻辑上说PhoneCall代表一个已经存在的电话。没有makeCall或者是newCallCome之前PhoneCall是不存在的。
顺便看看这个函数的实现,有几个地方有用到makeCall这里选取最典型的情况,按下拨出电话的按钮
makeCall:
(P)QtQuteCom::callButtonClicked()
(C)CUserProfile::makeCall(phoneNumber)
(C)switch context to Qutecom call CUserProfile::makeCallThreadSafe()
(M)UserProfile.makeCall
(M)PhoneLine::makeCall === 这里new PhoneCall
(SIPWrapper)sipWrapper->makeCall
End owphone
总结上面的几个函数:
①几个动作的流程都是 Qt界面(P) --> Control(CPhoneCall) --> PostEvent(to Qutecom)后返回
--> (QuteCom执行)Model(PhoneCall) --> Model(PhoneLine) --> SIPWrapper
②由此可见QuteCom作为中介者执行Control派发的各种指令并在QuteCom的上下文内执行Model的代码,隔绝了视图(P)不和模型(M)的联系,故而P和M不知道对方的存在。
但是P&C,C&M是互相认识的。这里通过巧妙的PostEvent将处理该Event的函数对象传给中介者(Qutecom),因而QuteCom执行的代码却是在其他模块内避免了QuteCom变的臃肿。
③这里也可以看出Qutecom对象(线程对象)的重要性,它作为M被所有的C认识,当有指令从C传递下到M时,如果需要异步处理
则可以通过PostEvent交给QuteCom线程处理,自己则立即返回。C亦可直接调用C(一个功能模块的C&M互相认识,如CPhoneCall和PhoneCall);
二、记录状态变化
PhoneCall记录电话状态,并且作为观察者模式下的被观察者(Subject)。必须记录各种状态,并且提供一个注册观察者的接口,当自身的状态发生变化时,通过接口将
自身的状态信息通知所有观察者。这里的注册接口即为:
Event<void (PhoneCall & sender, EnumPhoneCallState::PhoneCallState status)> stateChangedEvent;
Event<void (PhoneCall & sender, piximage * remoteVideoFrame, piximage * localVideoFrame)> videoFrameReceivedEvent;
stateChangedEvent是一个从boost::signal派生的信号。所有观察者可以通过connect和disconnect这个信号(Event重载了+=和-=)
得到状态变化的信息(EnumPhoneCallState::PhoneCallState status))。
videoFrameReceivedEvent记录有新的视频帧来临时的信号,这里略过。
stateChangedEvent唯一被PhoneCall::setState调用。
当被外界调用setState时,即CallState发生变化时,将做如下处理
(一)调用PhoneCallState::execute执行对象状态变化,调用applyState应用状态变化
(二)调用stateChangedEvent将状态变化通知观察者。
关于PhoneCallState对象:
PhoneCallState封装了电话状态对象,多个电话状态由此派生:
EnumPhoneCallState::PhoneCallState getCode() 返回表示状态的枚举
void execute(PhoneCall & phoneCall,bool doublecall = false) 执行状态变化时的动作(如停止播放声音,开始播放声音)
还有几个与播放声音相关的静态函数
static AudioDevice getRingerAudioDevice();
static void playSoundIncomingCall();
static void playSoundOutgoingCall();
static void playSoundDoubleCall();
static void playSoundCallClosed();
static void stopSound();
以及声音对象
static Sound * _sound;
各个PhoneCallState的派生类对象构成一个静态列表_phoneCallStateList,该列表包括以下对象:
static PhoneCallStateUnknown stateUnknown;
static PhoneCallStateClosed stateClosed;
static PhoneCallStateDialing stateDialing;
static PhoneCallStateError stateError;
static PhoneCallStateHold stateHold;
static PhoneCallStateIncoming stateIncoming;
static PhoneCallStateTalking stateTalking;
static PhoneCallStateResumed stateResumed;
static PhoneCallStateRinging stateRinging;
static PhoneCallStateRingingStart stateRingingStart;
static PhoneCallStateRingingStop stateRingingStop;
static PhoneCallStateBusy stateBusy;
static PhoneCallStateRejected stateRejected;
static PhoneCallStateUserNotFound stateUserNotFound;
static PhoneCallStateUserNotAvailable stateUserNotAvailable;
每个派生类都实现了getCode()和execute()以对应不同的电话状态以及状态改变时所采取的动作。
例如:
当SIP栈收到电话状态改变的通知时将以此调用
_sipWrapper.phoneCallStateChangedEvent --(connect)--SipCallbacks::phoneCallStateChangedEventHandler
-->PhoneLine::setPhoneCallState
-->phoneCall::setState()
-->stateChangedEvent
-->phoneCall::getCode -- check PhoneCall state
-->phoneCall::execute()
-->phoneCall::applyState();
stateChangedEvent被以下几个观察者监视
①ConferenceCall::phoneCallStateChangedEventHandler
②ConferenceCallParticipant::phoneCallStateChangedEventHandler
③CPhoneCall::stateChangedEventHandler
目前没有用到会议,因此略过①和②看CPhoneCall::stateChangedEventHandler
(M)PhoneCall::stateChangedEvent
(C)CPhoneCall::stateChangedEventHandler
(P)PFactory::postEvent(event) -- switch context to QApplication thread
(P)CPhoneCall::stateChangedEventHandlerThreadSafe
(P)QtPhoneCall::stateChangedEvent
如上所示,stateChangedEvent逐步调用Control和Present的函数,将状态变化传递给视图。需要注意PFactory::postEvent最后调用的是QApplication::PostEvent,
故而postEvent后的代码是在Qt GUI线程中执行的。