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()