Add currentspeakermonitor.
git-svn-id: http://libjingle.googlecode.com/svn/trunk@69 dd674b97-3498-5ee5-1854-bdd07cd0ff33
diff --git a/talk/examples/call/callclient.cc b/talk/examples/call/callclient.cc
index 65bca1f..da2716f 100644
--- a/talk/examples/call/callclient.cc
+++ b/talk/examples/call/callclient.cc
@@ -899,8 +899,8 @@
void CallClient::OnMediaSourcesUpdate(cricket::Call* call,
cricket::Session* session,
const cricket::MediaSources& sources) {
- for (cricket::NamedSources::const_iterator it = sources.video.begin();
- it != sources.video.end(); ++it) {
+ for (cricket::NamedSources::const_iterator it = sources.video().begin();
+ it != sources.video().end(); ++it) {
if (it->removed) {
RemoveStaticRenderedView(it->ssrc);
} else {
diff --git a/talk/libjingle.scons b/talk/libjingle.scons
index 1b7f10b..44259de 100644
--- a/talk/libjingle.scons
+++ b/talk/libjingle.scons
@@ -182,6 +182,7 @@
"session/phone/channel.cc",
"session/phone/channelmanager.cc",
"session/phone/codec.cc",
+ "session/phone/currentspeakermonitor.cc",
"session/phone/devicemanager.cc",
"session/phone/filemediaengine.cc",
"session/phone/mediaengine.cc",
diff --git a/talk/session/phone/call.cc b/talk/session/phone/call.cc
index 08658ff..27ea76f 100644
--- a/talk/session/phone/call.cc
+++ b/talk/session/phone/call.cc
@@ -137,9 +137,9 @@
StaticVideoViews::const_iterator it;
for (it = view_request.static_video_views.begin();
it != view_request.static_video_views.end(); ++it) {
- const NamedSource* found_source =
- media_sources_.GetVideoSourceBySsrc(it->ssrc);
- if (!found_source) {
+ NamedSource found_source;
+ bool found = media_sources_.GetVideoSourceBySsrc(it->ssrc, &found_source);
+ if (!found) {
LOG(LS_WARNING) <<
"Tried sending view request for bad ssrc: " << it->ssrc;
return false;
@@ -544,62 +544,68 @@
}
NamedSources::iterator it;
- for (it = sources.audio.begin(); it != sources.audio.end(); ++it) {
- const NamedSource* found;
+ for (it = sources.mutable_audio()->begin();
+ it != sources.mutable_audio()->end(); ++it) {
+ bool found = false;
+ NamedSource found_source;
if (it->ssrc_set) {
- found = media_sources_.GetAudioSourceBySsrc(it->ssrc);
+ found = media_sources_.GetAudioSourceBySsrc(it->ssrc, &found_source);
} else {
// For backwards compatibility, we remove by nick.
// TODO: Remove once all senders use explicit remove by ssrc.
- found = media_sources_.GetFirstAudioSourceByNick(it->nick);
+ found = media_sources_.GetFirstAudioSourceByNick(it->nick,
+ &found_source);
if (found) {
- it->SetSsrc(found->ssrc);
+ it->SetSsrc(found_source.ssrc);
it->removed = true;
} else {
continue; // No ssrc to remove.
}
}
if (it->removed && found) {
- RemoveVoiceStream(session, found->ssrc);
+ RemoveVoiceStream(session, found_source.ssrc);
media_sources_.RemoveAudioSourceBySsrc(it->ssrc);
- updates.audio.push_back(*it);
- LOG(LS_INFO) << "Removed voice stream: " << found->ssrc;
+ updates.mutable_audio()->push_back(*it);
+ LOG(LS_INFO) << "Removed voice stream: " << found_source.ssrc;
} else if (!it->removed && !found) {
AddVoiceStream(session, it->ssrc);
media_sources_.AddAudioSource(*it);
- updates.audio.push_back(*it);
+ updates.mutable_audio()->push_back(*it);
LOG(LS_INFO) << "Added voice stream: " << it->ssrc;
}
}
- for (it = sources.video.begin(); it != sources.video.end(); ++it) {
- const NamedSource* found;
+ for (it = sources.mutable_video()->begin();
+ it != sources.mutable_video()->end(); ++it) {
+ bool found = false;
+ NamedSource found_source;
if (it->ssrc_set) {
- found = media_sources_.GetVideoSourceBySsrc(it->ssrc);
+ found = media_sources_.GetVideoSourceBySsrc(it->ssrc, &found_source);
} else {
// For backwards compatibility, we remove by nick.
// TODO: Remove once all senders use explicit remove by ssrc.
- found = media_sources_.GetFirstVideoSourceByNick(it->nick);
+ found = media_sources_.GetFirstVideoSourceByNick(it->nick,
+ &found_source);
if (found) {
- it->SetSsrc(found->ssrc);
+ it->SetSsrc(found_source.ssrc);
it->removed = true;
} else {
continue; // No ssrc to remove.
}
}
if (it->removed && found) {
- RemoveVideoStream(session, found->ssrc);
+ RemoveVideoStream(session, found_source.ssrc);
media_sources_.RemoveVideoSourceBySsrc(it->ssrc);
- updates.video.push_back(*it);
- LOG(LS_INFO) << "Removed video stream: " << found->ssrc;
+ updates.mutable_video()->push_back(*it);
+ LOG(LS_INFO) << "Removed video stream: " << found_source.ssrc;
} else if (!it->removed && !found) {
AddVideoStream(session, it->ssrc);
media_sources_.AddVideoSource(*it);
- updates.video.push_back(*it);
+ updates.mutable_video()->push_back(*it);
LOG(LS_INFO) << "Added video stream: " << it->ssrc;
}
}
- if (!updates.audio.empty() || !updates.video.empty()) {
+ if (!updates.audio().empty() || !updates.video().empty()) {
SignalMediaSourcesUpdate(this, session, updates);
}
}
diff --git a/talk/session/phone/currentspeakermonitor.cc b/talk/session/phone/currentspeakermonitor.cc
new file mode 100644
index 0000000..d2d4d9a
--- /dev/null
+++ b/talk/session/phone/currentspeakermonitor.cc
@@ -0,0 +1,205 @@
+/*
+ * libjingle
+ * Copyright 2011, 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 "talk/session/phone/currentspeakermonitor.h"
+#include "talk/base/logging.h"
+
+namespace cricket {
+
+namespace {
+const int kMaxAudioLevel = 9;
+// To avoid overswitching, we disable switching for a period of time after a
+// switch is done.
+const int kDefaultMinTimeBetweenSwitches = 1000;
+}
+
+CurrentSpeakerMonitor::CurrentSpeakerMonitor(Call* call, Session* session)
+ : started_(false),
+ call_(call),
+ session_(session),
+ current_speaker_ssrc_(0),
+ earliest_permitted_switch_time_(0),
+ min_time_between_switches_(kDefaultMinTimeBetweenSwitches) {
+}
+
+CurrentSpeakerMonitor::~CurrentSpeakerMonitor() {
+ Stop();
+}
+
+void CurrentSpeakerMonitor::Start() {
+ if (!started_) {
+ call_->SignalAudioMonitor.connect(
+ this, &CurrentSpeakerMonitor::OnAudioMonitor);
+ call_->SignalMediaSourcesUpdate.connect(
+ this, &CurrentSpeakerMonitor::OnMediaSourcesUpdate);
+
+ started_ = true;
+ }
+}
+
+void CurrentSpeakerMonitor::Stop() {
+ if (started_) {
+ call_->SignalAudioMonitor.disconnect(this);
+ call_->SignalMediaSourcesUpdate.disconnect(this);
+
+ started_ = false;
+ ssrc_to_speaking_state_map_.clear();
+ current_speaker_ssrc_ = 0;
+ earliest_permitted_switch_time_ = 0;
+ }
+}
+
+void CurrentSpeakerMonitor::set_min_time_between_switches(
+ uint32 min_time_between_switches) {
+ min_time_between_switches_ = min_time_between_switches;
+}
+
+void CurrentSpeakerMonitor::OnAudioMonitor(Call* call, const AudioInfo& info) {
+ std::map<uint32, int> active_ssrc_to_level_map;
+ cricket::AudioInfo::StreamList::const_iterator stream_list_it;
+ for (stream_list_it = info.active_streams.begin();
+ stream_list_it != info.active_streams.end(); ++stream_list_it) {
+ uint32 ssrc = stream_list_it->first;
+ active_ssrc_to_level_map[ssrc] = stream_list_it->second;
+
+ // It's possible we haven't yet added this source to our map. If so,
+ // add it now with a "not speaking" state.
+ if (ssrc_to_speaking_state_map_.find(ssrc) ==
+ ssrc_to_speaking_state_map_.end()) {
+ ssrc_to_speaking_state_map_[ssrc] = SS_NOT_SPEAKING;
+ }
+ }
+
+ int max_level = 0;
+ uint32 loudest_speaker_ssrc = 0;
+
+ // Update the speaking states of all participants based on the new audio
+ // level information. Also retain loudest speaker.
+ std::map<uint32, SpeakingState>::iterator state_it;
+ for (state_it = ssrc_to_speaking_state_map_.begin();
+ state_it != ssrc_to_speaking_state_map_.end(); ++state_it) {
+ bool is_previous_speaker = current_speaker_ssrc_ == state_it->first;
+
+ // This uses a state machine in order to gradually identify members as
+ // having started or stopped speaking.
+
+ std::map<uint32, int>::const_iterator level_it =
+ active_ssrc_to_level_map.find(state_it->first);
+ // Note that the stream map only contains streams with non-zero audio
+ // levels.
+ int level = (level_it != active_ssrc_to_level_map.end()) ?
+ level_it->second : 0;
+ switch (state_it->second) {
+ case SS_NOT_SPEAKING:
+ if (level > 0) {
+ // Reset level because we don't think they're really speaking.
+ level = 0;
+ state_it->second = SS_MIGHT_BE_SPEAKING;
+ } else {
+ // State unchanged.
+ }
+ break;
+ case SS_MIGHT_BE_SPEAKING:
+ if (level > 0) {
+ state_it->second = SS_SPEAKING;
+ } else {
+ state_it->second = SS_NOT_SPEAKING;
+ }
+ break;
+ case SS_SPEAKING:
+ if (level > 0) {
+ // State unchanged.
+ } else {
+ state_it->second = SS_WAS_SPEAKING_RECENTLY1;
+ if (is_previous_speaker) {
+ // Assume this is an inter-word silence and assign him the highest
+ // volume.
+ level = kMaxAudioLevel;
+ }
+ }
+ break;
+ case SS_WAS_SPEAKING_RECENTLY1:
+ if (level > 0) {
+ state_it->second = SS_SPEAKING;
+ } else {
+ state_it->second = SS_WAS_SPEAKING_RECENTLY2;
+ if (is_previous_speaker) {
+ // Assume this is an inter-word silence and assign him the highest
+ // volume.
+ level = kMaxAudioLevel;
+ }
+ }
+ break;
+ case SS_WAS_SPEAKING_RECENTLY2:
+ if (level > 0) {
+ state_it->second = SS_SPEAKING;
+ } else {
+ state_it->second = SS_NOT_SPEAKING;
+ }
+ break;
+ }
+
+ if (level > max_level) {
+ loudest_speaker_ssrc = state_it->first;
+ max_level = level;
+ } else if (level > 0 && level == max_level && is_previous_speaker) {
+ // Favor continuity of loudest speakers if audio levels are equal.
+ loudest_speaker_ssrc = state_it->first;
+ }
+ }
+
+ // We avoid over-switching by disabling switching for a period of time after
+ // a switch is done.
+ uint32 now = talk_base::Time();
+ if (earliest_permitted_switch_time_ <= now &&
+ current_speaker_ssrc_ != loudest_speaker_ssrc) {
+ current_speaker_ssrc_ = loudest_speaker_ssrc;
+ LOG(LS_INFO) << "Current speaker changed to " << current_speaker_ssrc_;
+ earliest_permitted_switch_time_ = now + min_time_between_switches_;
+ SignalUpdate(this, current_speaker_ssrc_);
+ }
+}
+
+void CurrentSpeakerMonitor::OnMediaSourcesUpdate(Call* call, Session* session,
+ const MediaSources& sources) {
+ if (call == call_ && session == session_) {
+ // Update the speaking state map based on new or removed sources.
+ NamedSources::const_iterator it;
+ for (it = sources.audio().begin(); it != sources.audio().end(); it++) {
+ if (it->ssrc_set) {
+ if (it->removed) {
+ ssrc_to_speaking_state_map_.erase(it->ssrc);
+ } else if (ssrc_to_speaking_state_map_.find(it->ssrc) ==
+ ssrc_to_speaking_state_map_.begin()) {
+ ssrc_to_speaking_state_map_[it->ssrc] = SS_NOT_SPEAKING;
+ }
+ }
+ }
+ }
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/currentspeakermonitor.h b/talk/session/phone/currentspeakermonitor.h
new file mode 100644
index 0000000..36e23d9
--- /dev/null
+++ b/talk/session/phone/currentspeakermonitor.h
@@ -0,0 +1,91 @@
+/*
+ * libjingle
+ * Copyright 2011, 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.
+ */
+
+// CurrentSpeakerMonitor monitors the audio levels for a session and determines
+// which participant is currently speaking.
+
+#ifndef TALK_SESSION_PHONE_CURRENTSPEAKERMONITOR_H_
+#define TALK_SESSION_PHONE_CURRENTSPEAKERMONITOR_H_
+
+#include <map>
+
+#include "talk/session/phone/call.h"
+#include "talk/base/sigslot.h"
+
+namespace cricket {
+
+// Note that the call's audio monitor must be started before this is started.
+// It's recommended that the audio monitor be started with a 100 ms period.
+class CurrentSpeakerMonitor : public sigslot::has_slots<> {
+ public:
+ CurrentSpeakerMonitor(Call* call, Session* session);
+ ~CurrentSpeakerMonitor();
+
+ void Start();
+ void Stop();
+
+ // Used by tests. Note that the actual minimum time between switches
+ // enforced by the monitor will be the given value plus or minus the
+ // resolution of the system clock.
+ void set_min_time_between_switches(uint32 min_time_between_switches);
+
+ // This is fired when the current speaker changes, and provides his audio
+ // SSRC. This only fires after the audio monitor on the underlying Call has
+ // been started.
+ sigslot::signal2<CurrentSpeakerMonitor*, uint32> SignalUpdate;
+
+ private:
+ void OnAudioMonitor(Call* call, const AudioInfo& info);
+ void OnMediaSourcesUpdate(Call* call,
+ Session* session,
+ const MediaSources& sources);
+
+ // These are states that a participant will pass through so that we gradually
+ // recognize that they have started and stopped speaking. This avoids
+ // "twitchiness".
+ enum SpeakingState {
+ SS_NOT_SPEAKING,
+ SS_MIGHT_BE_SPEAKING,
+ SS_SPEAKING,
+ SS_WAS_SPEAKING_RECENTLY1,
+ SS_WAS_SPEAKING_RECENTLY2
+ };
+
+ bool started_;
+ Call* call_;
+ Session* session_;
+ std::map<uint32, SpeakingState> ssrc_to_speaking_state_map_;
+ uint32 current_speaker_ssrc_;
+ // To prevent overswitching, switching is disabled for some time after a
+ // switch is made. This gives us the earliest time a switch is permitted.
+ uint32 earliest_permitted_switch_time_;
+ uint32 min_time_between_switches_;
+};
+
+}
+
+#endif // TALK_SESSION_PHONE_CURRENTSPEAKERMONITOR_H_
diff --git a/talk/session/phone/mediaengine.cc b/talk/session/phone/mediaengine.cc
index 43d75e1..6db0825 100644
--- a/talk/session/phone/mediaengine.cc
+++ b/talk/session/phone/mediaengine.cc
@@ -27,18 +27,17 @@
#include "talk/session/phone/mediaengine.h"
-#ifdef HAVE_LINPHONE
+#if defined(HAVE_LINPHONE)
#include "talk/session/phone/linphonemediaengine.h"
-#endif
-#ifdef HAVE_WEBRTC
+#elif defined(HAVE_WEBRTC)
#include "talk/session/phone/webrtcvoiceengine.h"
#include "talk/session/phone/webrtcvideoengine.h"
#if defined(PLATFORM_CHROMIUM)
#include "content/renderer/renderer_webrtc_audio_device_impl.h"
+#else // Other browsers
+#endif // PLATFORM_CHROMIUM
#else
-// Other browsers
-#endif
-#endif
+#endif // HAVE_LINPHONE
namespace cricket {
#if defined(PLATFORM_CHROMIUM)
@@ -48,22 +47,22 @@
ChromiumWebRtcVoiceEngine() : WebRtcVoiceEngine(
new RendererWebRtcAudioDeviceImpl(1440, 1440, 1, 1, 48000, 48000)) {}
};
-#else
-// Other browsers
-#endif
+#else // Other browsers
+#endif // PLATFORM_CHROMIUM
MediaEngine* MediaEngine::Create() {
-#if defined(HAVE_LINPHONE)
- return new LinphoneMediaEngine("", "");
+#if defined(ANDROID)
+ return AndroidMediaEngineFactory::Create();
#elif defined(HAVE_WEBRTC)
-#if defined(PLATFORM_CHROMIUM)
+ return new CompositeMediaEngine<WebRtcVoiceEngine, WebRtcVideoEngine>();
+#elif defined(PLATFORM_CHROMIUM)
return new CompositeMediaEngine<ChromiumWebRtcVoiceEngine,
WebRtcVideoEngine>();
-#else
- return new CompositeMediaEngine<WebRtcVoiceEngine, WebRtcVideoEngine>();
-#endif
+#elif defined(HAVE_LINPHONE)
+ return new LinphoneMediaEngine("", "");
#else
return new NullMediaEngine();
-#endif
+#endif // ANDROID or HAVE_WEBRTC or PLATFORM_CHROMIUM or HAVE_LINPHONE
}
+
}; // namespace cricket
diff --git a/talk/session/phone/mediamessages.cc b/talk/session/phone/mediamessages.cc
index b1e9b76..9031351 100644
--- a/talk/session/phone/mediamessages.cc
+++ b/talk/session/phone/mediamessages.cc
@@ -25,6 +25,10 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+/*
+ * Documentation is in mediamessages.h.
+ */
+
#include "talk/session/phone/mediamessages.h"
#include "talk/base/stringencode.h"
@@ -34,43 +38,31 @@
namespace cricket {
-const NamedSource* GetFirstSourceByNick(const NamedSources& sources,
- const std::string& nick) {
+namespace {
+
+bool GetFirstSourceByNick(const NamedSources& sources,
+ const std::string& nick,
+ NamedSource* source_out) {
for (NamedSources::const_iterator source = sources.begin();
source != sources.end(); ++source) {
if (source->nick == nick) {
- return &*source;
+ *source_out = *source;
+ return true;
}
}
- return NULL;
+ return false;
}
-const NamedSource* GetSourceBySsrc(const NamedSources& sources, uint32 ssrc) {
+bool GetSourceBySsrc(const NamedSources& sources, uint32 ssrc,
+ NamedSource* source_out) {
for (NamedSources::const_iterator source = sources.begin();
source != sources.end(); ++source) {
if (source->ssrc == ssrc) {
- return &*source;
+ *source_out = *source;
+ return true;
}
}
- return NULL;
-}
-
-const NamedSource* MediaSources::GetFirstAudioSourceByNick(
- const std::string& nick) {
- return GetFirstSourceByNick(audio, nick);
-}
-
-const NamedSource* MediaSources::GetFirstVideoSourceByNick(
- const std::string& nick) {
- return GetFirstSourceByNick(video, nick);
-}
-
-const NamedSource* MediaSources::GetAudioSourceBySsrc(uint32 ssrc) {
- return GetSourceBySsrc(audio, ssrc);
-}
-
-const NamedSource* MediaSources::GetVideoSourceBySsrc(uint32 ssrc) {
- return GetSourceBySsrc(video, ssrc);
+ return false;
}
// NOTE: There is no check here for duplicate sources, so check before
@@ -79,15 +71,7 @@
sources->push_back(source);
}
-void MediaSources::AddAudioSource(const NamedSource& source) {
- AddSource(&audio, source);
-}
-
-void MediaSources::AddVideoSource(const NamedSource& source) {
- AddSource(&video, source);
-}
-
-void RemoveSourceBySsrc(NamedSources* sources, uint32 ssrc) {
+void RemoveSourceBySsrc(uint32 ssrc, NamedSources* sources) {
for (NamedSources::iterator source = sources->begin();
source != sources->end(); ) {
if (source->ssrc == ssrc) {
@@ -98,14 +82,6 @@
}
}
-void MediaSources::RemoveAudioSourceBySsrc(uint32 ssrc) {
- RemoveSourceBySsrc(&audio, ssrc);
-}
-
-void MediaSources::RemoveVideoSourceBySsrc(uint32 ssrc) {
- RemoveSourceBySsrc(&video, ssrc);
-}
-
bool ParseSsrc(const std::string& string, uint32* ssrc) {
return talk_base::FromString(string, ssrc);
}
@@ -128,8 +104,8 @@
named_source->name = source_elem->Attr(QN_JINGLE_DRAFT_SOURCE_NAME);
named_source->usage = source_elem->Attr(QN_JINGLE_DRAFT_SOURCE_USAGE);
named_source->removed =
- (STR_JINGLE_DRAFT_SOURCE_STATE_REMOVED ==
- source_elem->Attr(QN_JINGLE_DRAFT_SOURCE_STATE));
+ STR_JINGLE_DRAFT_SOURCE_STATE_REMOVED ==
+ source_elem->Attr(QN_JINGLE_DRAFT_SOURCE_STATE);
const buzz::XmlElement* ssrc_elem =
source_elem->FirstNamed(QN_JINGLE_DRAFT_SOURCE_SSRC);
@@ -144,48 +120,8 @@
return true;
}
-bool IsSourcesNotify(const buzz::XmlElement* action_elem) {
- return action_elem->FirstNamed(QN_JINGLE_DRAFT_NOTIFY) != NULL;
-}
-
-bool ParseSourcesNotify(const buzz::XmlElement* action_elem,
- const SessionDescription* session_description,
- MediaSources* sources,
- ParseError* error) {
- for (const buzz::XmlElement* notify_elem
- = action_elem->FirstNamed(QN_JINGLE_DRAFT_NOTIFY);
- notify_elem != NULL;
- notify_elem = notify_elem->NextNamed(QN_JINGLE_DRAFT_NOTIFY)) {
- std::string content_name = notify_elem->Attr(QN_JINGLE_DRAFT_CONTENT_NAME);
- for (const buzz::XmlElement* source_elem
- = notify_elem->FirstNamed(QN_JINGLE_DRAFT_SOURCE);
- source_elem != NULL;
- source_elem = source_elem->NextNamed(QN_JINGLE_DRAFT_SOURCE)) {
- NamedSource named_source;
- if (!ParseNamedSource(source_elem, &named_source, error)) {
- return false;
- }
-
- if (session_description == NULL) {
- return BadParse("unknown content name: " + content_name, error);
- }
- const ContentInfo* content =
- FindContentInfoByName(session_description->contents(), content_name);
- if (content == NULL) {
- return BadParse("unknown content name: " + content_name, error);
- }
-
- if (IsAudioContent(content)) {
- sources->audio.push_back(named_source);
- } else if (IsVideoContent(content)) {
- sources->video.push_back(named_source);
- }
- }
- }
-
- return true;
-}
-
+// Builds a <view> element according to the following spec:
+// goto/jinglemuc
buzz::XmlElement* CreateViewElem(const std::string& name,
const std::string& type) {
buzz::XmlElement* view_elem =
@@ -223,11 +159,52 @@
return view_elem;
}
+} // namespace
+
+bool MediaSources::GetFirstAudioSourceByNick(
+ const std::string& nick, NamedSource* source) {
+ return GetFirstSourceByNick(audio_, nick, source);
+}
+
+bool MediaSources::GetFirstVideoSourceByNick(
+ const std::string& nick, NamedSource* source) {
+ return GetFirstSourceByNick(video_, nick, source);
+}
+
+void MediaSources::CopyFrom(const MediaSources& sources) {
+ audio_ = sources.audio_;
+ video_ = sources.video_;
+}
+
+bool MediaSources::GetAudioSourceBySsrc(uint32 ssrc, NamedSource* source) {
+ return GetSourceBySsrc(audio_, ssrc, source);
+}
+
+bool MediaSources::GetVideoSourceBySsrc(uint32 ssrc, NamedSource* source) {
+ return GetSourceBySsrc(video_, ssrc, source);
+}
+
+void MediaSources::AddAudioSource(const NamedSource& source) {
+ AddSource(&audio_, source);
+}
+
+void MediaSources::AddVideoSource(const NamedSource& source) {
+ AddSource(&video_, source);
+}
+
+void MediaSources::RemoveAudioSourceBySsrc(uint32 ssrc) {
+ RemoveSourceBySsrc(ssrc, &audio_);
+}
+
+void MediaSources::RemoveVideoSourceBySsrc(uint32 ssrc) {
+ RemoveSourceBySsrc(ssrc, &video_);
+}
+
bool WriteViewRequest(const std::string& content_name,
const ViewRequest& request,
XmlElements* elems,
WriteError* error) {
- if (request.static_video_views.size() == 0) {
+ if (request.static_video_views.empty()) {
elems->push_back(CreateNoneVideoViewElem(content_name));
} else {
for (StaticVideoViews::const_iterator view =
@@ -239,4 +216,46 @@
return true;
}
+bool IsSourcesNotify(const buzz::XmlElement* action_elem) {
+ return action_elem->FirstNamed(QN_JINGLE_DRAFT_NOTIFY) != NULL;
+}
+
+bool ParseSourcesNotify(const buzz::XmlElement* action_elem,
+ const SessionDescription* session_description,
+ MediaSources* sources,
+ ParseError* error) {
+ for (const buzz::XmlElement* notify_elem =
+ action_elem->FirstNamed(QN_JINGLE_DRAFT_NOTIFY);
+ notify_elem != NULL;
+ notify_elem = notify_elem->NextNamed(QN_JINGLE_DRAFT_NOTIFY)) {
+ std::string content_name = notify_elem->Attr(QN_JINGLE_DRAFT_CONTENT_NAME);
+ for (const buzz::XmlElement* source_elem =
+ notify_elem->FirstNamed(QN_JINGLE_DRAFT_SOURCE);
+ source_elem != NULL;
+ source_elem = source_elem->NextNamed(QN_JINGLE_DRAFT_SOURCE)) {
+ NamedSource named_source;
+ if (!ParseNamedSource(source_elem, &named_source, error)) {
+ return false;
+ }
+
+ if (session_description == NULL) {
+ return BadParse("Unknown content name: " + content_name, error);
+ }
+ const ContentInfo* content =
+ FindContentInfoByName(session_description->contents(), content_name);
+ if (content == NULL) {
+ return BadParse("Unknown content name: " + content_name, error);
+ }
+
+ if (IsAudioContent(content)) {
+ sources->mutable_audio()->push_back(named_source);
+ } else if (IsVideoContent(content)) {
+ sources->mutable_video()->push_back(named_source);
+ }
+ }
+ }
+
+ return true;
+}
+
} // namespace cricket
diff --git a/talk/session/phone/mediamessages.h b/talk/session/phone/mediamessages.h
index 58e8793..541572f 100644
--- a/talk/session/phone/mediamessages.h
+++ b/talk/session/phone/mediamessages.h
@@ -25,17 +25,28 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+/*
+ * A collection of functions and types for serializing and
+ * deserializing Jingle session messages related to media.
+ * Specificially, the <notify> and <view> messages. They are not yet
+ * standardized, but their current documentation can be found at:
+ * goto/jinglemuc
+ */
+
#ifndef TALK_SESSION_PHONE_MEDIAMESSAGES_H_
#define TALK_SESSION_PHONE_MEDIAMESSAGES_H_
#include <string>
#include <vector>
+
#include "talk/base/basictypes.h"
#include "talk/p2p/base/parsing.h"
#include "talk/p2p/base/sessiondescription.h"
namespace cricket {
+// In a <notify> message, there are number of sources with names.
+// This represents one such named source.
struct NamedSource {
NamedSource() : ssrc(0), ssrc_set(false), removed(false) {}
@@ -52,23 +63,49 @@
bool removed;
};
+// TODO: Remove this, according to c++ readability.
typedef std::vector<NamedSource> NamedSources;
-class MediaSources {
+// A collection of named audio sources and named video sources, as
+// would be found in a typical <notify> message. Most of the methods
+// are merely for convenience. Many of these methods are keyed by
+// ssrc, which is the source identifier in the RTP spec
+// (http://tools.ietf.org/html/rfc3550).
+struct MediaSources {
public:
- const NamedSource* GetAudioSourceBySsrc(uint32 ssrc);
- const NamedSource* GetVideoSourceBySsrc(uint32 ssrc);
- // TODO: Remove once all senders use excplict remove by ssrc.
- const NamedSource* GetFirstAudioSourceByNick(const std::string& nick);
- const NamedSource* GetFirstVideoSourceByNick(const std::string& nick);
+ MediaSources() {}
+ void CopyFrom(const MediaSources& sources);
+
+ NamedSources* mutable_audio() { return &audio_; }
+ NamedSources* mutable_video() { return &video_; }
+ const NamedSources& audio() const { return audio_; }
+ const NamedSources& video() const { return video_; }
+
+ // Get the source with the given ssrc. Returns true if found.
+ bool GetAudioSourceBySsrc(uint32 ssrc, NamedSource* source);
+ bool GetVideoSourceBySsrc(uint32 ssrc, NamedSource* source);
+ // Get the first source with the given nick. Returns true if found.
+ // TODO: Remove the following two methods once all
+ // senders use explicit-remove by ssrc.
+ bool GetFirstAudioSourceByNick(const std::string& nick, NamedSource* source);
+ bool GetFirstVideoSourceByNick(const std::string& nick, NamedSource* source);
+ // Add a source.
void AddAudioSource(const NamedSource& source);
void AddVideoSource(const NamedSource& source);
+ // Remove the source with the given ssrc.
void RemoveAudioSourceBySsrc(uint32 ssrc);
void RemoveVideoSourceBySsrc(uint32 ssrc);
- NamedSources audio;
- NamedSources video;
+
+ private:
+ NamedSources audio_;
+ NamedSources video_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaSources);
};
+// In a <view> message, there are a number of views specified. This
+// represents one such view. We currently only support "static"
+// views.
struct StaticVideoView {
StaticVideoView(uint32 ssrc, int width, int height, int framerate)
: ssrc(ssrc),
@@ -86,21 +123,28 @@
typedef std::vector<StaticVideoView> StaticVideoViews;
+// Represents a whole <view> message, which contains many views.
struct ViewRequest {
StaticVideoViews static_video_views;
};
+// Serializes a view request to XML. If it fails, returns false and
+// fills in an error message.
bool WriteViewRequest(const std::string& content_name,
const ViewRequest& view,
XmlElements* elems,
WriteError* error);
bool IsSourcesNotify(const buzz::XmlElement* action_elem);
+
+// Parses a notify message from XML. If it fails, returns false and
+// fills in an error message.
// The session_description is needed to map content_name => media type.
bool ParseSourcesNotify(const buzz::XmlElement* action_elem,
const SessionDescription* session_description,
MediaSources* sources,
ParseError* error);
+
} // namespace cricket
#endif // TALK_SESSION_PHONE_MEDIAMESSAGES_H_
diff --git a/talk/session/phone/srtpfilter.cc b/talk/session/phone/srtpfilter.cc
index 19ee1f4..3b46e57 100644
--- a/talk/session/phone/srtpfilter.cc
+++ b/talk/session/phone/srtpfilter.cc
@@ -95,6 +95,7 @@
#endif // !HAVE_SRTP
void EnableSrtpDebugging() {
+#ifdef _DEBUG
debug_on(mod_srtp);
debug_on(mod_auth);
debug_on(mod_cipher);
@@ -103,6 +104,7 @@
debug_on(mod_aes_icm);
// debug_on(mod_aes_cbc);
// debug_on(mod_hmac);
+#endif
}
SrtpFilter::SrtpFilter()