| /* |
| * libjingle |
| * Copyright 2004 Google Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |
| * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
| * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include <string> |
| #include "webrtc/base/helpers.h" |
| #include "webrtc/base/logging.h" |
| #include "webrtc/base/thread.h" |
| #include "webrtc/base/window.h" |
| #include "talk/media/base/constants.h" |
| #include "talk/media/base/screencastid.h" |
| #include "talk/p2p/base/parsing.h" |
| #include "talk/session/media/call.h" |
| #include "talk/session/media/currentspeakermonitor.h" |
| #include "talk/session/media/mediasessionclient.h" |
| |
| namespace cricket { |
| |
| const uint32 MSG_CHECKAUTODESTROY = 1; |
| const uint32 MSG_TERMINATECALL = 2; |
| const uint32 MSG_PLAYDTMF = 3; |
| |
| namespace { |
| const int kDTMFDelay = 300; // msec |
| const size_t kMaxDTMFDigits = 30; |
| const int kSendToVoicemailTimeout = 1000*20; |
| const int kNoVoicemailTimeout = 1000*180; |
| const int kMediaMonitorInterval = 1000*15; |
| // In order to be the same as the server-side switching, this must be 100. |
| const int kAudioMonitorPollPeriodMillis = 100; |
| |
| // V is a pointer type. |
| template<class K, class V> |
| V FindOrNull(const std::map<K, V>& map, |
| const K& key) { |
| typename std::map<K, V>::const_iterator it = map.find(key); |
| return (it != map.end()) ? it->second : NULL; |
| } |
| |
| |
| bool ContentContainsCrypto(const cricket::ContentInfo* content) { |
| if (content != NULL) { |
| const cricket::MediaContentDescription* desc = |
| static_cast<const cricket::MediaContentDescription*>( |
| content->description); |
| if (!desc || desc->cryptos().empty()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| } |
| |
| AudioSourceProxy::AudioSourceProxy(Call* call) |
| : call_(call) { |
| call_->SignalAudioMonitor.connect(this, &AudioSourceProxy::OnAudioMonitor); |
| call_->SignalMediaStreamsUpdate.connect( |
| this, &AudioSourceProxy::OnMediaStreamsUpdate); |
| } |
| |
| void AudioSourceProxy::OnAudioMonitor(Call* call, const AudioInfo& info) { |
| SignalAudioMonitor(this, info); |
| } |
| |
| void AudioSourceProxy::OnMediaStreamsUpdate(Call* call, Session* session, |
| const MediaStreams& added, const MediaStreams& removed) { |
| SignalMediaStreamsUpdate(this, session, added, removed); |
| } |
| |
| Call::Call(MediaSessionClient* session_client) |
| : id_(rtc::CreateRandomId()), |
| session_client_(session_client), |
| local_renderer_(NULL), |
| has_video_(false), |
| has_data_(false), |
| muted_(false), |
| video_muted_(false), |
| send_to_voicemail_(true), |
| playing_dtmf_(false) { |
| audio_source_proxy_.reset(new AudioSourceProxy(this)); |
| } |
| |
| Call::~Call() { |
| while (media_session_map_.begin() != media_session_map_.end()) { |
| Session* session = media_session_map_.begin()->second.session; |
| RemoveSession(session); |
| session_client_->session_manager()->DestroySession(session); |
| } |
| rtc::Thread::Current()->Clear(this); |
| } |
| |
| Session* Call::InitiateSession(const buzz::Jid& to, |
| const buzz::Jid& initiator, |
| const CallOptions& options) { |
| std::string id; |
| std::string initiator_name = initiator.Str(); |
| return InternalInitiateSession(id, to, initiator_name, options); |
| } |
| |
| Session *Call::InitiateSession(const std::string& id, |
| const buzz::Jid& to, |
| const CallOptions& options) { |
| std::string initiator_name; |
| return InternalInitiateSession(id, to, initiator_name, options); |
| } |
| |
| void Call::IncomingSession(Session* session, const SessionDescription* offer) { |
| AddSession(session, offer); |
| |
| // Make sure the session knows about the incoming ssrcs. This needs to be done |
| // prior to the SignalSessionState call, because that may trigger handling of |
| // these new SSRCs, so they need to be registered before then. |
| UpdateRemoteMediaStreams(session, offer->contents(), false); |
| |
| // Missed the first state, the initiate, which is needed by |
| // call_client. |
| SignalSessionState(this, session, Session::STATE_RECEIVEDINITIATE); |
| } |
| |
| void Call::AcceptSession(Session* session, |
| const cricket::CallOptions& options) { |
| MediaSessionMap::iterator it = media_session_map_.find(session->id()); |
| if (it != media_session_map_.end()) { |
| const SessionDescription* answer = session_client_->CreateAnswer( |
| session->remote_description(), options); |
| it->second.session->Accept(answer); |
| } |
| } |
| |
| void Call::RejectSession(Session* session) { |
| // Assume polite decline. |
| MediaSessionMap::iterator it = media_session_map_.find(session->id()); |
| if (it != media_session_map_.end()) |
| it->second.session->Reject(STR_TERMINATE_DECLINE); |
| } |
| |
| void Call::TerminateSession(Session* session) { |
| MediaSessionMap::iterator it = media_session_map_.find(session->id()); |
| if (it != media_session_map_.end()) { |
| // Assume polite terminations. |
| it->second.session->Terminate(); |
| } |
| } |
| |
| void Call::Terminate() { |
| // Copy the list so that we can iterate over it in a stable way |
| std::vector<Session*> sessions = this->sessions(); |
| |
| // There may be more than one session to terminate |
| std::vector<Session*>::iterator it; |
| for (it = sessions.begin(); it != sessions.end(); ++it) { |
| TerminateSession(*it); |
| } |
| } |
| |
| bool Call::SendViewRequest(Session* session, |
| const ViewRequest& view_request) { |
| StaticVideoViews::const_iterator it; |
| for (it = view_request.static_video_views.begin(); |
| it != view_request.static_video_views.end(); ++it) { |
| StreamParams found_stream; |
| bool found = false; |
| MediaStreams* recv_streams = GetMediaStreams(session); |
| if (recv_streams) |
| found = recv_streams->GetVideoStream(it->selector, &found_stream); |
| if (!found) { |
| LOG(LS_WARNING) << "Trying to send view request for (" |
| << it->selector.ssrc << ", '" |
| << it->selector.groupid << "', '" |
| << it->selector.streamid << "'" |
| << ") is not in the local streams."; |
| return false; |
| } |
| } |
| |
| XmlElements elems; |
| WriteError error; |
| if (!WriteJingleViewRequest(CN_VIDEO, view_request, &elems, &error)) { |
| LOG(LS_ERROR) << "Couldn't write out view request: " << error.text; |
| return false; |
| } |
| |
| return session->SendInfoMessage(elems, session->remote_name()); |
| } |
| |
| void Call::SetLocalRenderer(VideoRenderer* renderer) { |
| local_renderer_ = renderer; |
| if (session_client_->GetFocus() == this) { |
| session_client_->channel_manager()->SetLocalRenderer(renderer); |
| } |
| } |
| |
| void Call::SetVideoRenderer(Session* session, uint32 ssrc, |
| VideoRenderer* renderer) { |
| VideoChannel* video_channel = GetVideoChannel(session); |
| if (video_channel) { |
| video_channel->SetRenderer(ssrc, renderer); |
| LOG(LS_INFO) << "Set renderer of ssrc " << ssrc |
| << " to " << renderer << "."; |
| } else { |
| LOG(LS_INFO) << "Failed to set renderer of ssrc " << ssrc << "."; |
| } |
| } |
| |
| void Call::OnMessage(rtc::Message* message) { |
| switch (message->message_id) { |
| case MSG_CHECKAUTODESTROY: |
| // If no more sessions for this call, delete it |
| if (media_session_map_.empty()) |
| session_client_->DestroyCall(this); |
| break; |
| case MSG_TERMINATECALL: |
| // Signal to the user that a timeout has happened and the call should |
| // be sent to voicemail. |
| if (send_to_voicemail_) { |
| SignalSetupToCallVoicemail(); |
| } |
| |
| // Callee didn't answer - terminate call |
| Terminate(); |
| break; |
| case MSG_PLAYDTMF: |
| ContinuePlayDTMF(); |
| } |
| } |
| |
| std::vector<Session*> Call::sessions() { |
| std::vector<Session*> sessions; |
| MediaSessionMap::iterator it; |
| for (it = media_session_map_.begin(); it != media_session_map_.end(); ++it) |
| sessions.push_back(it->second.session); |
| |
| return sessions; |
| } |
| |
| bool Call::AddSession(Session* session, const SessionDescription* offer) { |
| bool succeeded = true; |
| MediaSession media_session; |
| media_session.session = session; |
| media_session.voice_channel = NULL; |
| media_session.video_channel = NULL; |
| media_session.data_channel = NULL; |
| media_session.recv_streams = NULL; |
| |
| const ContentInfo* audio_offer = GetFirstAudioContent(offer); |
| const ContentInfo* video_offer = GetFirstVideoContent(offer); |
| const ContentInfo* data_offer = GetFirstDataContent(offer); |
| has_video_ = (video_offer != NULL); |
| has_data_ = (data_offer != NULL); |
| |
| ASSERT(audio_offer != NULL); |
| // Create voice channel and start a media monitor. |
| media_session.voice_channel = |
| session_client_->channel_manager()->CreateVoiceChannel( |
| session, audio_offer->name, has_video_); |
| // voice_channel can be NULL in case of NullVoiceEngine. |
| if (media_session.voice_channel) { |
| media_session.voice_channel->SignalMediaMonitor.connect( |
| this, &Call::OnMediaMonitor); |
| media_session.voice_channel->StartMediaMonitor(kMediaMonitorInterval); |
| } else { |
| succeeded = false; |
| } |
| |
| // If desired, create video channel and start a media monitor. |
| if (has_video_ && succeeded) { |
| media_session.video_channel = |
| session_client_->channel_manager()->CreateVideoChannel( |
| session, video_offer->name, true, media_session.voice_channel); |
| // video_channel can be NULL in case of NullVideoEngine. |
| if (media_session.video_channel) { |
| media_session.video_channel->SignalMediaMonitor.connect( |
| this, &Call::OnMediaMonitor); |
| media_session.video_channel->StartMediaMonitor(kMediaMonitorInterval); |
| } else { |
| succeeded = false; |
| } |
| } |
| |
| // If desired, create data channel. |
| if (has_data_ && succeeded) { |
| const DataContentDescription* data = GetFirstDataContentDescription(offer); |
| if (data == NULL) { |
| succeeded = false; |
| } else { |
| DataChannelType data_channel_type = DCT_RTP; |
| if ((data->protocol() == kMediaProtocolSctp) || |
| (data->protocol() == kMediaProtocolDtlsSctp)) { |
| data_channel_type = DCT_SCTP; |
| } |
| |
| bool rtcp = false; |
| media_session.data_channel = |
| session_client_->channel_manager()->CreateDataChannel( |
| session, data_offer->name, rtcp, data_channel_type); |
| if (media_session.data_channel) { |
| media_session.data_channel->SignalDataReceived.connect( |
| this, &Call::OnDataReceived); |
| } else { |
| succeeded = false; |
| } |
| } |
| } |
| |
| if (succeeded) { |
| // Add session to list, create channels for this session. |
| media_session.recv_streams = new MediaStreams; |
| media_session_map_[session->id()] = media_session; |
| session->SignalState.connect(this, &Call::OnSessionState); |
| session->SignalError.connect(this, &Call::OnSessionError); |
| session->SignalInfoMessage.connect( |
| this, &Call::OnSessionInfoMessage); |
| session->SignalRemoteDescriptionUpdate.connect( |
| this, &Call::OnRemoteDescriptionUpdate); |
| session->SignalReceivedTerminateReason |
| .connect(this, &Call::OnReceivedTerminateReason); |
| |
| // If this call has the focus, enable this session's channels. |
| if (session_client_->GetFocus() == this) { |
| EnableSessionChannels(session, true); |
| } |
| |
| // Signal client. |
| SignalAddSession(this, session); |
| } |
| |
| return succeeded; |
| } |
| |
| void Call::RemoveSession(Session* session) { |
| MediaSessionMap::iterator it = media_session_map_.find(session->id()); |
| if (it == media_session_map_.end()) |
| return; |
| |
| // Remove all the screencasts, if they haven't been already. |
| while (!it->second.started_screencasts.empty()) { |
| uint32 ssrc = it->second.started_screencasts.begin()->first; |
| if (!StopScreencastWithoutSendingUpdate(it->second.session, ssrc)) { |
| LOG(LS_ERROR) << "Unable to stop screencast with ssrc " << ssrc; |
| ASSERT(false); |
| } |
| } |
| |
| // Destroy video channel |
| VideoChannel* video_channel = it->second.video_channel; |
| if (video_channel != NULL) |
| session_client_->channel_manager()->DestroyVideoChannel(video_channel); |
| |
| // Destroy voice channel |
| VoiceChannel* voice_channel = it->second.voice_channel; |
| if (voice_channel != NULL) |
| session_client_->channel_manager()->DestroyVoiceChannel(voice_channel); |
| |
| // Destroy data channel |
| DataChannel* data_channel = it->second.data_channel; |
| if (data_channel != NULL) |
| session_client_->channel_manager()->DestroyDataChannel(data_channel); |
| |
| delete it->second.recv_streams; |
| media_session_map_.erase(it); |
| |
| // Destroy speaker monitor |
| StopSpeakerMonitor(session); |
| |
| // Signal client |
| SignalRemoveSession(this, session); |
| |
| // The call auto destroys when the last session is removed |
| rtc::Thread::Current()->Post(this, MSG_CHECKAUTODESTROY); |
| } |
| |
| VoiceChannel* Call::GetVoiceChannel(Session* session) const { |
| MediaSessionMap::const_iterator it = media_session_map_.find(session->id()); |
| return (it != media_session_map_.end()) ? it->second.voice_channel : NULL; |
| } |
| |
| VideoChannel* Call::GetVideoChannel(Session* session) const { |
| MediaSessionMap::const_iterator it = media_session_map_.find(session->id()); |
| return (it != media_session_map_.end()) ? it->second.video_channel : NULL; |
| } |
| |
| DataChannel* Call::GetDataChannel(Session* session) const { |
| MediaSessionMap::const_iterator it = media_session_map_.find(session->id()); |
| return (it != media_session_map_.end()) ? it->second.data_channel : NULL; |
| } |
| |
| MediaStreams* Call::GetMediaStreams(Session* session) const { |
| MediaSessionMap::const_iterator it = media_session_map_.find(session->id()); |
| return (it != media_session_map_.end()) ? it->second.recv_streams : NULL; |
| } |
| |
| void Call::EnableChannels(bool enable) { |
| MediaSessionMap::iterator it; |
| for (it = media_session_map_.begin(); it != media_session_map_.end(); ++it) { |
| EnableSessionChannels(it->second.session, enable); |
| } |
| session_client_->channel_manager()->SetLocalRenderer( |
| (enable) ? local_renderer_ : NULL); |
| } |
| |
| void Call::EnableSessionChannels(Session* session, bool enable) { |
| MediaSessionMap::iterator it = media_session_map_.find(session->id()); |
| if (it == media_session_map_.end()) |
| return; |
| |
| VoiceChannel* voice_channel = it->second.voice_channel; |
| VideoChannel* video_channel = it->second.video_channel; |
| DataChannel* data_channel = it->second.data_channel; |
| if (voice_channel != NULL) |
| voice_channel->Enable(enable); |
| if (video_channel != NULL) |
| video_channel->Enable(enable); |
| if (data_channel != NULL) |
| data_channel->Enable(enable); |
| } |
| |
| void Call::Mute(bool mute) { |
| muted_ = mute; |
| MediaSessionMap::iterator it; |
| for (it = media_session_map_.begin(); it != media_session_map_.end(); ++it) { |
| if (it->second.voice_channel != NULL) |
| it->second.voice_channel->MuteStream(0, mute); |
| } |
| } |
| |
| void Call::MuteVideo(bool mute) { |
| video_muted_ = mute; |
| MediaSessionMap::iterator it; |
| for (it = media_session_map_.begin(); it != media_session_map_.end(); ++it) { |
| if (it->second.video_channel != NULL) |
| it->second.video_channel->MuteStream(0, mute); |
| } |
| } |
| |
| bool Call::SendData(Session* session, |
| const SendDataParams& params, |
| const rtc::Buffer& payload, |
| SendDataResult* result) { |
| DataChannel* data_channel = GetDataChannel(session); |
| if (!data_channel) { |
| LOG(LS_WARNING) << "Could not send data: no data channel."; |
| return false; |
| } |
| |
| return data_channel->SendData(params, payload, result); |
| } |
| |
| void Call::PressDTMF(int event) { |
| // Queue up this digit |
| if (queued_dtmf_.size() < kMaxDTMFDigits) { |
| LOG(LS_INFO) << "Call::PressDTMF(" << event << ")"; |
| |
| queued_dtmf_.push_back(event); |
| |
| if (!playing_dtmf_) { |
| ContinuePlayDTMF(); |
| } |
| } |
| } |
| |
| cricket::VideoFormat ScreencastFormatFromFps(int fps) { |
| // The capturer pretty much ignore this, but just in case we give it |
| // a resolution big enough to cover any expected desktop. In any |
| // case, it can't be 0x0, or the CaptureManager will fail to use it. |
| return cricket::VideoFormat( |
| 1, 1, |
| cricket::VideoFormat::FpsToInterval(fps), cricket::FOURCC_ANY); |
| } |
| |
| bool Call::StartScreencast(Session* session, |
| const std::string& streamid, uint32 ssrc, |
| const ScreencastId& screencastid, int fps) { |
| MediaSessionMap::iterator it = media_session_map_.find(session->id()); |
| if (it == media_session_map_.end()) { |
| return false; |
| } |
| |
| VideoChannel *video_channel = GetVideoChannel(session); |
| if (!video_channel) { |
| LOG(LS_WARNING) << "Cannot add screencast" |
| << " because there is no video channel."; |
| return false; |
| } |
| |
| VideoCapturer *capturer = video_channel->AddScreencast(ssrc, screencastid); |
| if (capturer == NULL) { |
| LOG(LS_WARNING) << "Could not create screencast capturer."; |
| return false; |
| } |
| |
| VideoFormat format = ScreencastFormatFromFps(fps); |
| if (!session_client_->channel_manager()->StartVideoCapture( |
| capturer, format)) { |
| LOG(LS_WARNING) << "Could not start video capture."; |
| video_channel->RemoveScreencast(ssrc); |
| return false; |
| } |
| |
| if (!video_channel->SetCapturer(ssrc, capturer)) { |
| LOG(LS_WARNING) << "Could not start sending screencast."; |
| session_client_->channel_manager()->StopVideoCapture( |
| capturer, ScreencastFormatFromFps(fps)); |
| video_channel->RemoveScreencast(ssrc); |
| } |
| |
| // TODO(pthatcher): Once the CaptureManager has a nicer interface |
| // for removing captures (such as having StartCapture return a |
| // handle), remove this StartedCapture stuff. |
| it->second.started_screencasts.insert( |
| std::make_pair(ssrc, StartedCapture(capturer, format))); |
| |
| // TODO(pthatcher): Verify we aren't re-using an existing id or |
| // ssrc. |
| StreamParams stream; |
| stream.id = streamid; |
| stream.ssrcs.push_back(ssrc); |
| VideoContentDescription* video = CreateVideoStreamUpdate(stream); |
| |
| // TODO(pthatcher): Wait until view request before sending video. |
| video_channel->SetLocalContent(video, CA_UPDATE, NULL); |
| SendVideoStreamUpdate(session, video); |
| return true; |
| } |
| |
| bool Call::StopScreencast(Session* session, |
| const std::string& streamid, uint32 ssrc) { |
| if (!StopScreencastWithoutSendingUpdate(session, ssrc)) { |
| return false; |
| } |
| |
| VideoChannel *video_channel = GetVideoChannel(session); |
| if (!video_channel) { |
| LOG(LS_WARNING) << "Cannot add screencast" |
| << " because there is no video channel."; |
| return false; |
| } |
| |
| StreamParams stream; |
| stream.id = streamid; |
| // No ssrcs |
| VideoContentDescription* video = CreateVideoStreamUpdate(stream); |
| |
| video_channel->SetLocalContent(video, CA_UPDATE, NULL); |
| SendVideoStreamUpdate(session, video); |
| return true; |
| } |
| |
| bool Call::StopScreencastWithoutSendingUpdate( |
| Session* session, uint32 ssrc) { |
| MediaSessionMap::iterator it = media_session_map_.find(session->id()); |
| if (it == media_session_map_.end()) { |
| return false; |
| } |
| |
| VideoChannel *video_channel = GetVideoChannel(session); |
| if (!video_channel) { |
| LOG(LS_WARNING) << "Cannot remove screencast" |
| << " because there is no video channel."; |
| return false; |
| } |
| |
| StartedScreencastMap::const_iterator screencast_iter = |
| it->second.started_screencasts.find(ssrc); |
| if (screencast_iter == it->second.started_screencasts.end()) { |
| LOG(LS_WARNING) << "Could not stop screencast " << ssrc |
| << " because there is no capturer."; |
| return false; |
| } |
| |
| VideoCapturer* capturer = screencast_iter->second.capturer; |
| VideoFormat format = screencast_iter->second.format; |
| video_channel->SetCapturer(ssrc, NULL); |
| if (!session_client_->channel_manager()->StopVideoCapture( |
| capturer, format)) { |
| LOG(LS_WARNING) << "Could not stop screencast " << ssrc |
| << " because could not stop capture."; |
| return false; |
| } |
| video_channel->RemoveScreencast(ssrc); |
| it->second.started_screencasts.erase(ssrc); |
| return true; |
| } |
| |
| VideoContentDescription* Call::CreateVideoStreamUpdate( |
| const StreamParams& stream) { |
| VideoContentDescription* video = new VideoContentDescription(); |
| video->set_multistream(true); |
| video->set_partial(true); |
| video->AddStream(stream); |
| return video; |
| } |
| |
| void Call::SendVideoStreamUpdate( |
| Session* session, VideoContentDescription* video) { |
| // Takes the ownership of |video|. |
| rtc::scoped_ptr<VideoContentDescription> description(video); |
| const ContentInfo* video_info = |
| GetFirstVideoContent(session->local_description()); |
| if (video_info == NULL) { |
| LOG(LS_WARNING) << "Cannot send stream update for video."; |
| return; |
| } |
| |
| std::vector<ContentInfo> contents; |
| contents.push_back( |
| ContentInfo(video_info->name, video_info->type, description.get())); |
| |
| session->SendDescriptionInfoMessage(contents); |
| } |
| |
| void Call::ContinuePlayDTMF() { |
| playing_dtmf_ = false; |
| |
| // Check to see if we have a queued tone |
| if (queued_dtmf_.size() > 0) { |
| playing_dtmf_ = true; |
| |
| int tone = queued_dtmf_.front(); |
| queued_dtmf_.pop_front(); |
| |
| LOG(LS_INFO) << "Call::ContinuePlayDTMF(" << tone << ")"; |
| for (MediaSessionMap::iterator it = media_session_map_.begin(); |
| it != media_session_map_.end(); ++it) { |
| if (it->second.voice_channel != NULL) { |
| it->second.voice_channel->PressDTMF(tone, true); |
| } |
| } |
| |
| // Post a message to play the next tone or at least clear the playing_dtmf_ |
| // bit. |
| rtc::Thread::Current()->PostDelayed(kDTMFDelay, this, MSG_PLAYDTMF); |
| } |
| } |
| |
| void Call::Join(Call* call, bool enable) { |
| for (MediaSessionMap::iterator it = call->media_session_map_.begin(); |
| it != call->media_session_map_.end(); ++it) { |
| // Shouldn't already exist. |
| ASSERT(media_session_map_.find(it->first) == media_session_map_.end()); |
| media_session_map_[it->first] = it->second; |
| |
| it->second.session->SignalState.connect(this, &Call::OnSessionState); |
| it->second.session->SignalError.connect(this, &Call::OnSessionError); |
| it->second.session->SignalReceivedTerminateReason |
| .connect(this, &Call::OnReceivedTerminateReason); |
| |
| EnableSessionChannels(it->second.session, enable); |
| } |
| |
| // Moved all the sessions over, so the other call should no longer have any. |
| call->media_session_map_.clear(); |
| } |
| |
| void Call::StartConnectionMonitor(Session* session, int cms) { |
| VoiceChannel* voice_channel = GetVoiceChannel(session); |
| if (voice_channel) { |
| voice_channel->SignalConnectionMonitor.connect(this, |
| &Call::OnConnectionMonitor); |
| voice_channel->StartConnectionMonitor(cms); |
| } |
| |
| VideoChannel* video_channel = GetVideoChannel(session); |
| if (video_channel) { |
| video_channel->SignalConnectionMonitor.connect(this, |
| &Call::OnConnectionMonitor); |
| video_channel->StartConnectionMonitor(cms); |
| } |
| } |
| |
| void Call::StopConnectionMonitor(Session* session) { |
| VoiceChannel* voice_channel = GetVoiceChannel(session); |
| if (voice_channel) { |
| voice_channel->StopConnectionMonitor(); |
| voice_channel->SignalConnectionMonitor.disconnect(this); |
| } |
| |
| VideoChannel* video_channel = GetVideoChannel(session); |
| if (video_channel) { |
| video_channel->StopConnectionMonitor(); |
| video_channel->SignalConnectionMonitor.disconnect(this); |
| } |
| } |
| |
| void Call::StartAudioMonitor(Session* session, int cms) { |
| VoiceChannel* voice_channel = GetVoiceChannel(session); |
| if (voice_channel) { |
| voice_channel->SignalAudioMonitor.connect(this, &Call::OnAudioMonitor); |
| voice_channel->StartAudioMonitor(cms); |
| } |
| } |
| |
| void Call::StopAudioMonitor(Session* session) { |
| VoiceChannel* voice_channel = GetVoiceChannel(session); |
| if (voice_channel) { |
| voice_channel->StopAudioMonitor(); |
| voice_channel->SignalAudioMonitor.disconnect(this); |
| } |
| } |
| |
| bool Call::IsAudioMonitorRunning(Session* session) { |
| VoiceChannel* voice_channel = GetVoiceChannel(session); |
| if (voice_channel) { |
| return voice_channel->IsAudioMonitorRunning(); |
| } else { |
| return false; |
| } |
| } |
| |
| void Call::StartSpeakerMonitor(Session* session) { |
| if (speaker_monitor_map_.find(session->id()) == speaker_monitor_map_.end()) { |
| if (!IsAudioMonitorRunning(session)) { |
| StartAudioMonitor(session, kAudioMonitorPollPeriodMillis); |
| } |
| CurrentSpeakerMonitor* speaker_monitor = |
| new cricket::CurrentSpeakerMonitor( |
| audio_source_proxy_.get(), session); |
| speaker_monitor->SignalUpdate.connect(this, &Call::OnSpeakerMonitor); |
| speaker_monitor->Start(); |
| speaker_monitor_map_[session->id()] = speaker_monitor; |
| } else { |
| LOG(LS_WARNING) << "Already started speaker monitor for session " |
| << session->id() << "."; |
| } |
| } |
| |
| void Call::StopSpeakerMonitor(Session* session) { |
| if (speaker_monitor_map_.find(session->id()) == speaker_monitor_map_.end()) { |
| LOG(LS_WARNING) << "Speaker monitor for session " |
| << session->id() << " already stopped."; |
| } else { |
| CurrentSpeakerMonitor* monitor = speaker_monitor_map_[session->id()]; |
| monitor->Stop(); |
| speaker_monitor_map_.erase(session->id()); |
| delete monitor; |
| } |
| } |
| |
| void Call::OnConnectionMonitor(VoiceChannel* channel, |
| const std::vector<ConnectionInfo> &infos) { |
| SignalConnectionMonitor(this, infos); |
| } |
| |
| void Call::OnMediaMonitor(VoiceChannel* channel, const VoiceMediaInfo& info) { |
| last_voice_media_info_ = info; |
| SignalMediaMonitor(this, info); |
| } |
| |
| void Call::OnAudioMonitor(VoiceChannel* channel, const AudioInfo& info) { |
| SignalAudioMonitor(this, info); |
| } |
| |
| void Call::OnSpeakerMonitor(CurrentSpeakerMonitor* monitor, uint32 ssrc) { |
| Session* session = static_cast<Session*>(monitor->session()); |
| MediaStreams* recv_streams = GetMediaStreams(session); |
| if (recv_streams) { |
| StreamParams stream; |
| recv_streams->GetAudioStream(StreamSelector(ssrc), &stream); |
| SignalSpeakerMonitor(this, session, stream); |
| } |
| } |
| |
| void Call::OnConnectionMonitor(VideoChannel* channel, |
| const std::vector<ConnectionInfo> &infos) { |
| SignalVideoConnectionMonitor(this, infos); |
| } |
| |
| void Call::OnMediaMonitor(VideoChannel* channel, const VideoMediaInfo& info) { |
| SignalVideoMediaMonitor(this, info); |
| } |
| |
| void Call::OnDataReceived(DataChannel* channel, |
| const ReceiveDataParams& params, |
| const rtc::Buffer& payload) { |
| SignalDataReceived(this, params, payload); |
| } |
| |
| uint32 Call::id() { |
| return id_; |
| } |
| |
| void Call::OnSessionState(BaseSession* base_session, BaseSession::State state) { |
| Session* session = static_cast<Session*>(base_session); |
| switch (state) { |
| case Session::STATE_RECEIVEDACCEPT: |
| UpdateRemoteMediaStreams(session, |
| session->remote_description()->contents(), false); |
| session_client_->session_manager()->signaling_thread()->Clear(this, |
| MSG_TERMINATECALL); |
| break; |
| case Session::STATE_RECEIVEDREJECT: |
| case Session::STATE_RECEIVEDTERMINATE: |
| session_client_->session_manager()->signaling_thread()->Clear(this, |
| MSG_TERMINATECALL); |
| break; |
| default: |
| break; |
| } |
| SignalSessionState(this, session, state); |
| } |
| |
| void Call::OnSessionError(BaseSession* base_session, Session::Error error) { |
| session_client_->session_manager()->signaling_thread()->Clear(this, |
| MSG_TERMINATECALL); |
| SignalSessionError(this, static_cast<Session*>(base_session), error); |
| } |
| |
| void Call::OnSessionInfoMessage(Session* session, |
| const buzz::XmlElement* action_elem) { |
| if (!IsJingleViewRequest(action_elem)) { |
| return; |
| } |
| |
| ViewRequest view_request; |
| ParseError error; |
| if (!ParseJingleViewRequest(action_elem, &view_request, &error)) { |
| LOG(LS_WARNING) << "Failed to parse view request: " << error.text; |
| return; |
| } |
| |
| VideoChannel* video_channel = GetVideoChannel(session); |
| if (video_channel == NULL) { |
| LOG(LS_WARNING) << "Ignore view request since we have no video channel."; |
| return; |
| } |
| |
| if (!video_channel->ApplyViewRequest(view_request)) { |
| LOG(LS_WARNING) << "Failed to ApplyViewRequest."; |
| } |
| } |
| |
| void Call::OnRemoteDescriptionUpdate(BaseSession* base_session, |
| const ContentInfos& updated_contents) { |
| Session* session = static_cast<Session*>(base_session); |
| |
| const ContentInfo* audio_content = GetFirstAudioContent(updated_contents); |
| if (audio_content) { |
| const AudioContentDescription* audio_update = |
| static_cast<const AudioContentDescription*>(audio_content->description); |
| if (!audio_update->codecs().empty()) { |
| UpdateVoiceChannelRemoteContent(session, audio_update); |
| } |
| } |
| |
| const ContentInfo* video_content = GetFirstVideoContent(updated_contents); |
| if (video_content) { |
| const VideoContentDescription* video_update = |
| static_cast<const VideoContentDescription*>(video_content->description); |
| if (!video_update->codecs().empty()) { |
| UpdateVideoChannelRemoteContent(session, video_update); |
| } |
| } |
| |
| const ContentInfo* data_content = GetFirstDataContent(updated_contents); |
| if (data_content) { |
| const DataContentDescription* data_update = |
| static_cast<const DataContentDescription*>(data_content->description); |
| if (!data_update->codecs().empty()) { |
| UpdateDataChannelRemoteContent(session, data_update); |
| } |
| } |
| |
| UpdateRemoteMediaStreams(session, updated_contents, true); |
| } |
| |
| bool Call::UpdateVoiceChannelRemoteContent( |
| Session* session, const AudioContentDescription* audio) { |
| VoiceChannel* voice_channel = GetVoiceChannel(session); |
| if (!voice_channel->SetRemoteContent(audio, CA_UPDATE, NULL)) { |
| const std::string error_desc = |
| "Failure in audio SetRemoteContent with CA_UPDATE"; |
| LOG(LS_ERROR) << error_desc; |
| session->SetError(BaseSession::ERROR_CONTENT, error_desc); |
| return false; |
| } |
| return true; |
| } |
| |
| bool Call::UpdateVideoChannelRemoteContent( |
| Session* session, const VideoContentDescription* video) { |
| VideoChannel* video_channel = GetVideoChannel(session); |
| if (!video_channel->SetRemoteContent(video, CA_UPDATE, NULL)) { |
| const std::string error_desc = |
| "Failure in video SetRemoteContent with CA_UPDATE"; |
| LOG(LS_ERROR) << error_desc; |
| session->SetError(BaseSession::ERROR_CONTENT, error_desc); |
| return false; |
| } |
| return true; |
| } |
| |
| bool Call::UpdateDataChannelRemoteContent( |
| Session* session, const DataContentDescription* data) { |
| DataChannel* data_channel = GetDataChannel(session); |
| if (!data_channel->SetRemoteContent(data, CA_UPDATE, NULL)) { |
| const std::string error_desc = |
| "Failure in data SetRemoteContent with CA_UPDATE"; |
| LOG(LS_ERROR) << error_desc; |
| session->SetError(BaseSession::ERROR_CONTENT, error_desc); |
| return false; |
| } |
| return true; |
| } |
| |
| void Call::UpdateRemoteMediaStreams(Session* session, |
| const ContentInfos& updated_contents, |
| bool update_channels) { |
| MediaStreams* recv_streams = GetMediaStreams(session); |
| if (!recv_streams) |
| return; |
| |
| cricket::MediaStreams added_streams; |
| cricket::MediaStreams removed_streams; |
| |
| const ContentInfo* audio_content = GetFirstAudioContent(updated_contents); |
| if (audio_content) { |
| const AudioContentDescription* audio_update = |
| static_cast<const AudioContentDescription*>(audio_content->description); |
| UpdateRecvStreams(audio_update->streams(), |
| update_channels ? GetVoiceChannel(session) : NULL, |
| recv_streams->mutable_audio(), |
| added_streams.mutable_audio(), |
| removed_streams.mutable_audio()); |
| } |
| |
| const ContentInfo* video_content = GetFirstVideoContent(updated_contents); |
| if (video_content) { |
| const VideoContentDescription* video_update = |
| static_cast<const VideoContentDescription*>(video_content->description); |
| UpdateRecvStreams(video_update->streams(), |
| update_channels ? GetVideoChannel(session) : NULL, |
| recv_streams->mutable_video(), |
| added_streams.mutable_video(), |
| removed_streams.mutable_video()); |
| } |
| |
| const ContentInfo* data_content = GetFirstDataContent(updated_contents); |
| if (data_content) { |
| const DataContentDescription* data_update = |
| static_cast<const DataContentDescription*>(data_content->description); |
| UpdateRecvStreams(data_update->streams(), |
| update_channels ? GetDataChannel(session) : NULL, |
| recv_streams->mutable_data(), |
| added_streams.mutable_data(), |
| removed_streams.mutable_data()); |
| } |
| |
| if (!added_streams.empty() || !removed_streams.empty()) { |
| SignalMediaStreamsUpdate(this, session, added_streams, removed_streams); |
| } |
| } |
| |
| void FindStreamChanges(const std::vector<StreamParams>& streams, |
| const std::vector<StreamParams>& updates, |
| std::vector<StreamParams>* added_streams, |
| std::vector<StreamParams>* removed_streams) { |
| for (std::vector<StreamParams>::const_iterator update = updates.begin(); |
| update != updates.end(); ++update) { |
| StreamParams stream; |
| if (GetStreamByIds(streams, update->groupid, update->id, &stream)) { |
| if (!update->has_ssrcs()) { |
| removed_streams->push_back(stream); |
| } |
| } else { |
| // There's a bug on reflector that will send <stream>s even |
| // though there is not ssrc (which means there isn't really a |
| // stream). To work around it, we simply ignore new <stream>s |
| // that don't have any ssrcs. |
| if (update->has_ssrcs()) { |
| added_streams->push_back(*update); |
| } |
| } |
| } |
| } |
| |
| void Call::UpdateRecvStreams(const std::vector<StreamParams>& update_streams, |
| BaseChannel* channel, |
| std::vector<StreamParams>* recv_streams, |
| std::vector<StreamParams>* added_streams, |
| std::vector<StreamParams>* removed_streams) { |
| FindStreamChanges(*recv_streams, |
| update_streams, added_streams, removed_streams); |
| AddRecvStreams(*added_streams, |
| channel, recv_streams); |
| RemoveRecvStreams(*removed_streams, |
| channel, recv_streams); |
| } |
| |
| void Call::AddRecvStreams(const std::vector<StreamParams>& added_streams, |
| BaseChannel* channel, |
| std::vector<StreamParams>* recv_streams) { |
| std::vector<StreamParams>::const_iterator stream; |
| for (stream = added_streams.begin(); |
| stream != added_streams.end(); |
| ++stream) { |
| AddRecvStream(*stream, channel, recv_streams); |
| } |
| } |
| |
| void Call::AddRecvStream(const StreamParams& stream, |
| BaseChannel* channel, |
| std::vector<StreamParams>* recv_streams) { |
| if (channel && stream.has_ssrcs()) { |
| channel->AddRecvStream(stream); |
| } |
| recv_streams->push_back(stream); |
| } |
| |
| void Call::RemoveRecvStreams(const std::vector<StreamParams>& removed_streams, |
| BaseChannel* channel, |
| std::vector<StreamParams>* recv_streams) { |
| std::vector<StreamParams>::const_iterator stream; |
| for (stream = removed_streams.begin(); |
| stream != removed_streams.end(); |
| ++stream) { |
| RemoveRecvStream(*stream, channel, recv_streams); |
| } |
| } |
| |
| void Call::RemoveRecvStream(const StreamParams& stream, |
| BaseChannel* channel, |
| std::vector<StreamParams>* recv_streams) { |
| if (channel && stream.has_ssrcs()) { |
| // TODO(pthatcher): Change RemoveRecvStream to take a stream argument. |
| channel->RemoveRecvStream(stream.first_ssrc()); |
| } |
| RemoveStreamByIds(recv_streams, stream.groupid, stream.id); |
| } |
| |
| void Call::OnReceivedTerminateReason(Session* session, |
| const std::string& reason) { |
| session_client_->session_manager()->signaling_thread()->Clear(this, |
| MSG_TERMINATECALL); |
| SignalReceivedTerminateReason(this, session, reason); |
| } |
| |
| // TODO(mdodd): Get ride of this method since all Hangouts are using a secure |
| // connection. |
| bool Call::secure() const { |
| if (session_client_->secure() == SEC_DISABLED) { |
| return false; |
| } |
| |
| bool ret = true; |
| int i = 0; |
| |
| MediaSessionMap::const_iterator it; |
| for (it = media_session_map_.begin(); it != media_session_map_.end(); ++it) { |
| LOG_F(LS_VERBOSE) << "session[" << i |
| << "], check local and remote descriptions"; |
| i++; |
| |
| if (!SessionDescriptionContainsCrypto( |
| it->second.session->local_description()) || |
| !SessionDescriptionContainsCrypto( |
| it->second.session->remote_description())) { |
| ret = false; |
| break; |
| } |
| } |
| |
| LOG_F(LS_VERBOSE) << "secure=" << ret; |
| return ret; |
| } |
| |
| bool Call::SessionDescriptionContainsCrypto( |
| const SessionDescription* sdesc) const { |
| if (sdesc == NULL) { |
| LOG_F(LS_VERBOSE) << "sessionDescription is NULL"; |
| return false; |
| } |
| |
| return ContentContainsCrypto(sdesc->GetContentByName(CN_AUDIO)) && |
| ContentContainsCrypto(sdesc->GetContentByName(CN_VIDEO)); |
| } |
| |
| Session* Call::InternalInitiateSession(const std::string& id, |
| const buzz::Jid& to, |
| const std::string& initiator_name, |
| const CallOptions& options) { |
| const SessionDescription* offer = session_client_->CreateOffer(options); |
| |
| Session* session = session_client_->CreateSession(id, this); |
| // Only override the initiator_name if it was manually supplied. Otherwise, |
| // session_client_ will supply the local jid as initiator in CreateOffer. |
| if (!initiator_name.empty()) { |
| session->set_initiator_name(initiator_name); |
| } |
| |
| AddSession(session, offer); |
| session->Initiate(to.Str(), offer); |
| |
| // After this timeout, terminate the call because the callee isn't |
| // answering |
| session_client_->session_manager()->signaling_thread()->Clear(this, |
| MSG_TERMINATECALL); |
| session_client_->session_manager()->signaling_thread()->PostDelayed( |
| send_to_voicemail_ ? kSendToVoicemailTimeout : kNoVoicemailTimeout, |
| this, MSG_TERMINATECALL); |
| return session; |
| } |
| |
| AudioSourceProxy* Call::GetAudioSourceProxy() { |
| return audio_source_proxy_.get(); |
| } |
| |
| } // namespace cricket |