/* * 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 #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" #include "webrtc/base/helpers.h" #include "webrtc/base/logging.h" #include "webrtc/base/thread.h" #include "webrtc/base/window.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 V FindOrNull(const std::map& map, const K& key) { typename std::map::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( 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), 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 sessions = this->sessions(); // There may be more than one session to terminate std::vector::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::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 Call::sessions() { std::vector 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, VideoOptions(), 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); } } 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& screenid, 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 = session_client_->channel_manager()-> CreateScreenCapturer(screenid); if (!capturer) { LOG(LS_WARNING) << "Could not create screencast capturer."; return false; } if (!video_channel->AddScreencast(ssrc, capturer)) { delete capturer; LOG(LS_WARNING) << "Could not add 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 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 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 &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(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 &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(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(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(base_session); const ContentInfo* audio_content = GetFirstAudioContent(updated_contents); if (audio_content) { const AudioContentDescription* audio_update = static_cast(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(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(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(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(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(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& streams, const std::vector& updates, std::vector* added_streams, std::vector* removed_streams) { for (std::vector::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 s even // though there is not ssrc (which means there isn't really a // stream). To work around it, we simply ignore new s // that don't have any ssrcs. if (update->has_ssrcs()) { added_streams->push_back(*update); } } } } void Call::UpdateRecvStreams(const std::vector& update_streams, BaseChannel* channel, std::vector* recv_streams, std::vector* added_streams, std::vector* 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& added_streams, BaseChannel* channel, std::vector* recv_streams) { std::vector::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* recv_streams) { if (channel && stream.has_ssrcs()) { channel->AddRecvStream(stream); } recv_streams->push_back(stream); } void Call::RemoveRecvStreams(const std::vector& removed_streams, BaseChannel* channel, std::vector* recv_streams) { std::vector::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* 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