Support bandwidth control in call.exe and support call redirects.

git-svn-id: http://libjingle.googlecode.com/svn/trunk@38 dd674b97-3498-5ee5-1854-bdd07cd0ff33
diff --git a/talk/examples/call/callclient.cc b/talk/examples/call/callclient.cc
index 3657420..92fcea3 100644
--- a/talk/examples/call/callclient.cc
+++ b/talk/examples/call/callclient.cc
@@ -35,6 +35,7 @@
 #include "talk/base/network.h"
 #include "talk/base/socketaddress.h"
 #include "talk/base/stringutils.h"
+#include "talk/base/stringencode.h"
 #include "talk/p2p/base/sessionmanager.h"
 #include "talk/p2p/client/basicportallocator.h"
 #include "talk/p2p/client/sessionmanagertask.h"
@@ -82,6 +83,25 @@
   }
 }
 
+std::string GetWord(const std::vector<std::string>& words,
+                    size_t index, const std::string& def) {
+  if (words.size() > index) {
+    return words[index];
+  } else {
+    return def;
+  }
+}
+
+int GetInt(const std::vector<std::string>& words, size_t index, int def) {
+  int val;
+  if (words.size() > index && talk_base::FromString(words[index], &val)) {
+    return val;
+  } else {
+    return def;
+  }
+}
+
+
 }  // namespace
 
 const char* CALL_COMMANDS =
@@ -107,10 +127,10 @@
 "\n"
 "  roster              Prints the online friends from your roster.\n"
 "  friend user         Request to add a user to your roster.\n"
-"  call [jid]          Initiates a call to the user[/room] with the\n"
-"                      given JID.\n"
-"  vcall [jid]         Initiates a video call to the user[/room] with\n"
-"                      the given JID.\n"
+"  call [jid] [bw]     Initiates a call to the user[/room] with the\n"
+"                      given JID and with optional bandwidth.\n"
+"  vcall [jid] [bw]    Initiates a video call to the user[/room] with\n"
+"                      the given JID and with optional bandwidth.\n"
 "  voicemail [jid]     Leave a voicemail for the user with the given JID.\n"
 "  join [room]         Joins a multi-user-chat.\n"
 "  invite user [room]  Invites a friend to a multi-user-chat.\n"
@@ -142,37 +162,38 @@
   }
 
   // Global commands
-  if ((words.size() == 1) && (words[0] == "quit")) {
+  const std::string& command = GetWord(words, 0, "");
+  if (command == "quit") {
     Quit();
   } else if (call_ && incoming_call_) {
-    if ((words.size() == 1) && (words[0] == "accept")) {
+    if (command == "accept") {
       Accept();
-    } else if ((words.size() == 1) && (words[0] == "reject")) {
+    } else if (command == "reject") {
       Reject();
     } else {
       console_->Print(RECEIVE_COMMANDS);
     }
   } else if (call_) {
-    if ((words.size() == 1) && (words[0] == "hangup")) {
+    if (command == "hangup") {
       // TODO: do more shutdown here, move to Terminate()
       call_->Terminate();
       call_ = NULL;
       session_ = NULL;
       console_->SetPrompt(NULL);
-    } else if ((words.size() == 1) && (words[0] == "mute")) {
+    } else if (command == "mute") {
       call_->Mute(true);
-    } else if ((words.size() == 1) && (words[0] == "unmute")) {
+    } else if (command == "unmute") {
       call_->Mute(false);
-    } else if ((words.size() == 2) && (words[0] == "dtmf")) {
+    } else if ((command == "dtmf") && (words.size() == 2)) {
       int ev = std::string("0123456789*#").find(words[1][0]);
       call_->PressDTMF(ev);
     } else {
       console_->Print(CALL_COMMANDS);
     }
   } else {
-    if ((words.size() == 1) && (words[0] == "roster")) {
+    if (command == "roster") {
       PrintRoster();
-    } else if ((words.size() >= 2) && (words[0] == "send")) {
+    } else if (command == "send") {
       buzz::Jid jid(words[1]);
       if (jid.IsValid()) {
         last_sent_to_ = words[1];
@@ -183,23 +204,31 @@
         console_->Printf(
             "Invalid JID. JIDs should be in the form user@domain\n");
       }
-    } else if ((words.size() == 2) && (words[0] == "friend")) {
+    } else if ((words.size() == 2) && (command == "friend")) {
       InviteFriend(words[1]);
-    } else if ((words.size() >= 1) && (words[0] == "call")) {
-      MakeCallTo((words.size() >= 2) ? words[1] : "", false);
-    } else if ((words.size() >= 1) && (words[0] == "vcall")) {
-      MakeCallTo((words.size() >= 2) ? words[1] : "", true);
-    } else if ((words.size() >= 1) && (words[0] == "join")) {
-      JoinMuc((words.size() >= 2) ? words[1] : "");
-    } else if ((words.size() >= 2) && (words[0] == "invite")) {
-      InviteToMuc(words[1], (words.size() >= 3) ? words[2] : "");
-    } else if ((words.size() >= 1) && (words[0] == "leave")) {
-      LeaveMuc((words.size() >= 2) ? words[1] : "");
-    } else if ((words.size() == 1) && (words[0] == "getdevs")) {
+    } else if (command == "call") {
+      std::string to = GetWord(words, 1, "");
+      MakeCallTo(to, cricket::CallOptions());
+    } else if (command == "vcall") {
+      std::string to = GetWord(words, 1, "");
+      int bandwidth = GetInt(words, 2, -1);
+      cricket::CallOptions options;
+      options.is_video = true;
+      if (bandwidth > 0) {
+        options.video_bandwidth = bandwidth;
+      }
+      MakeCallTo(to, options);
+    } else if (command == "join") {
+      JoinMuc(GetWord(words, 1, ""));
+    } else if ((words.size() >= 2) && (command == "invite")) {
+      InviteToMuc(words[1], GetWord(words, 2, ""));
+    } else if (command == "leave") {
+      LeaveMuc(GetWord(words, 1, ""));
+    } else if (command == "getdevs") {
       GetDevices();
-    } else if ((words.size() == 2) && (words[0] == "setvol")) {
+    } else if ((words.size() == 2) && (command == "setvol")) {
       SetVolume(words[1]);
-    } else if ((words.size() >= 1) && (words[0] == "voicemail")) {
+    } else if (command == "voicemail") {
       CallVoicemail((words.size() >= 2) ? words[1] : "");
     } else {
       console_->Print(CONSOLE_COMMANDS);
@@ -505,16 +534,20 @@
   console_->Printf("Requesting to befriend %s.\n", name.c_str());
 }
 
-void CallClient::MakeCallTo(const std::string& name, bool video) {
+void CallClient::MakeCallTo(const std::string& name,
+                            const cricket::CallOptions& given_options) {
+  // Copy so we can change .is_muc.
+  cricket::CallOptions options = given_options;
+
   bool found = false;
-  bool is_muc = false;
+  options.is_muc = false;
   buzz::Jid callto_jid(name);
   buzz::Jid found_jid;
   if (name.length() == 0 && mucs_.size() > 0) {
     // if no name, and in a MUC, establish audio with the MUC
     found_jid = mucs_.begin()->first;
     found = true;
-    is_muc = true;
+    options.is_muc = true;
   } else if (name[0] == '+') {
     // if the first character is a +, assume it's a phone number
     found_jid = callto_jid;
@@ -539,28 +572,29 @@
           mucs_[callto_jid]->state() == buzz::Muc::MUC_JOINED) {
         found = true;
         found_jid = callto_jid;
-        is_muc = true;
+        options.is_muc = true;
       }
     }
   }
 
   if (found) {
-    console_->Printf("Found %s '%s'", is_muc ? "room" : "online friend",
+    console_->Printf("Found %s '%s'", options.is_muc ? "room" : "online friend",
         found_jid.Str().c_str());
-    PlaceCall(found_jid, is_muc, video);
+    PlaceCall(found_jid, options);
   } else {
     console_->Printf("Could not find online friend '%s'", name.c_str());
   }
 }
 
-void CallClient::PlaceCall(const buzz::Jid& jid, bool is_muc, bool video) {
+void CallClient::PlaceCall(const buzz::Jid& jid,
+                           const cricket::CallOptions& options) {
   media_client_->SignalCallDestroy.connect(
       this, &CallClient::OnCallDestroy);
   if (!call_) {
-    call_ = media_client_->CreateCall(video, is_muc);
+    call_ = media_client_->CreateCall();
     console_->SetPrompt(jid.Str().c_str());
-    session_ = call_->InitiateSession(jid);
-    if (is_muc) {
+    session_ = call_->InitiateSession(jid, options);
+    if (options.is_muc) {
       // If people in this room are already in a call, must add all their
       // streams.
       buzz::Muc::MemberMap& members = mucs_[jid]->members();
@@ -598,7 +632,7 @@
 void CallClient::OnFoundVoicemailJid(const buzz::Jid& to,
                                      const buzz::Jid& voicemail) {
   console_->Printf("Calling %s's voicemail.\n", to.Str().c_str());
-  PlaceCall(voicemail, false, false);
+  PlaceCall(voicemail, cricket::CallOptions());
 }
 
 void CallClient::OnVoicemailJidError(const buzz::Jid& to) {
diff --git a/talk/examples/call/callclient.h b/talk/examples/call/callclient.h
index b7dc716..bdae6dc 100644
--- a/talk/examples/call/callclient.h
+++ b/talk/examples/call/callclient.h
@@ -66,6 +66,7 @@
 class MediaSessionClient;
 class Receiver;
 class Call;
+struct CallOptions;
 class SessionManagerTask;
 enum SignalingProtocol;
 }
@@ -148,8 +149,8 @@
   static const std::string strerror(buzz::XmppEngine::Error err);
 
   void PrintRoster();
-  void MakeCallTo(const std::string& name, bool video);
-  void PlaceCall(const buzz::Jid& jid, bool is_muc, bool video);
+  void MakeCallTo(const std::string& name, const cricket::CallOptions& options);
+  void PlaceCall(const buzz::Jid& jid, const cricket::CallOptions& options);
   void CallVoicemail(const std::string& name);
   void Accept();
   void Reject();
diff --git a/talk/main.scons b/talk/main.scons
index 54b863a..a1348ff 100644
--- a/talk/main.scons
+++ b/talk/main.scons
@@ -142,6 +142,9 @@
     # this NTDDI version or else the headers
     # that LMI includes from it won't compile.
     'NTDDI_VERSION=NTDDI_WINXP',
+
+    # npapi.h requires the following:
+    '_WINDOWS',
   ],
   CPPPATH = [
     '$THIRD_PARTY/wtl_71/include',
diff --git a/talk/p2p/base/constants.cc b/talk/p2p/base/constants.cc
index 7430140..a0c4ba4 100644
--- a/talk/p2p/base/constants.cc
+++ b/talk/p2p/base/constants.cc
@@ -64,6 +64,10 @@
 const std::string GINGLE_ACTION_TERMINATE("terminate");
 const std::string GINGLE_ACTION_CANDIDATES("candidates");
 
+const std::string LN_ERROR("error");
+const buzz::QName QN_GINGLE_REDIRECT(true, NS_GINGLE, "redirect");
+const std::string STR_REDIRECT_PREFIX("xmpp:");
+
 // Session Contents (aka Gingle <session><description>
 //                   or Jingle <content><description>)
 const std::string LN_DESCRIPTION("description");
@@ -84,6 +88,7 @@
 const std::string PAYLOADTYPE_PARAMETER_HEIGHT("height");
 const std::string PAYLOADTYPE_PARAMETER_WIDTH("width");
 const std::string PAYLOADTYPE_PARAMETER_FRAMERATE("framerate");
+const std::string LN_BANDWIDTH("bandwidth");
 
 const std::string CN_AUDIO("audio");
 const std::string CN_VIDEO("video");
@@ -108,7 +113,6 @@
 const buzz::QName QN_GINGLE_VIDEO_PAYLOADTYPE(
     true, NS_GINGLE_VIDEO, LN_PAYLOADTYPE);
 const buzz::QName QN_GINGLE_VIDEO_SRCID(true, NS_GINGLE_VIDEO, "src-id");
-const buzz::QName QN_GINGLE_VIDEO_BANDWIDTH(true, NS_GINGLE_VIDEO, "bandwidth");
 
 // Crypto support.
 const buzz::QName QN_ENCRYPTION(true, NS_JINGLE_RTP, "encryption");
diff --git a/talk/p2p/base/constants.h b/talk/p2p/base/constants.h
index d4e600b..8355210 100644
--- a/talk/p2p/base/constants.h
+++ b/talk/p2p/base/constants.h
@@ -80,6 +80,9 @@
 extern const std::string GINGLE_ACTION_TERMINATE;
 extern const std::string GINGLE_ACTION_CANDIDATES;
 
+extern const std::string LN_ERROR;
+extern const buzz::QName QN_GINGLE_REDIRECT;
+extern const std::string STR_REDIRECT_PREFIX;
 
 // Session Contents (aka Gingle <session><description>
 //                   or Jingle <content><description>)
@@ -102,6 +105,7 @@
 extern const std::string PAYLOADTYPE_PARAMETER_HEIGHT;
 extern const std::string PAYLOADTYPE_PARAMETER_WIDTH;
 extern const std::string PAYLOADTYPE_PARAMETER_FRAMERATE;
+extern const std::string LN_BANDWIDTH;
 
 // CN_ == "content name".  When we initiate a session, we choose the
 // name, and when we receive a Gingle session, we provide default
diff --git a/talk/p2p/base/session.cc b/talk/p2p/base/session.cc
index a7bb92d..38b91ff 100644
--- a/talk/p2p/base/session.cc
+++ b/talk/p2p/base/session.cc
@@ -672,6 +672,20 @@
     return;
   }
 
+  // If the error is a session redirect, call OnRedirectError, which will
+  // continue the session with a new remote JID.
+  SessionRedirect redirect;
+  if (FindSessionRedirect(error_stanza, &redirect)) {
+    SessionError error;
+    if (!OnRedirectError(redirect, &error)) {
+      // TODO: Should we send a message back?  The standard
+      // says nothing about it.
+      LOG(LS_ERROR) << "Failed to redirect: " << error.text;
+      SetError(ERROR_RESPONSE);
+    }
+    return;
+  }
+
   std::string error_type = "cancel";
 
   const buzz::XmlElement* error = error_stanza->FirstNamed(buzz::QN_ERROR);
@@ -810,6 +824,32 @@
   return true;
 }
 
+bool BareJidsEqual(const std::string& name1,
+                   const std::string& name2) {
+  buzz::Jid jid1(name1);
+  buzz::Jid jid2(name2);
+
+  return jid1.IsValid() && jid2.IsValid() && jid1.BareEquals(jid2);
+}
+
+bool Session::OnRedirectError(const SessionRedirect& redirect,
+                              SessionError* error) {
+  MessageError message_error;
+  if (!CheckState(STATE_SENTINITIATE, &message_error)) {
+    return BadWrite(message_error.text, error);
+  }
+
+  if (!BareJidsEqual(remote_name_, redirect.target))
+    return BadWrite("Redirection not allowed: must be the same bare jid.",
+                    error);
+
+  // When we receive a redirect, we point the session at the new JID
+  // and resend the candidates.
+  remote_name_ = redirect.target;
+  return (SendInitiateMessage(local_description(), error) &&
+          ResendAllTransportInfoMessages(error));
+}
+
 bool Session::CheckState(State state, MessageError* error) {
   ASSERT(state_ == state);
   if (state_ != state) {
@@ -910,6 +950,25 @@
                              elems, error);
 }
 
+bool Session::ResendAllTransportInfoMessages(SessionError* error) {
+  for (TransportMap::iterator iter = transports_.begin();
+       iter != transports_.end(); ++iter) {
+    TransportProxy* transproxy = iter->second;
+    if (transproxy->sent_candidates().size() > 0) {
+      if (!SendTransportInfoMessage(
+              TransportInfo(
+                  transproxy->content_name(),
+                  transproxy->type(),
+                  transproxy->sent_candidates()),
+              error)) {
+        return false;
+      }
+      transproxy->ClearSentCandidates();
+    }
+  }
+  return true;
+}
+
 bool Session::SendMessage(ActionType type, const XmlElements& action_elems,
                           SessionError* error) {
   talk_base::scoped_ptr<buzz::XmlElement> stanza(
diff --git a/talk/p2p/base/session.h b/talk/p2p/base/session.h
index a72f751..ddbc663 100644
--- a/talk/p2p/base/session.h
+++ b/talk/p2p/base/session.h
@@ -439,6 +439,7 @@
   bool SendTerminateMessage(const std::string& reason, SessionError* error);
   bool SendTransportInfoMessage(const TransportInfo& tinfo,
                                 SessionError* error);
+  bool ResendAllTransportInfoMessages(SessionError* error);
 
   // Both versions of SendMessage send a message of the given type to
   // the other client.  Can pass either a set of elements or an
@@ -509,6 +510,7 @@
   bool OnTerminateMessage(const SessionMessage& msg, MessageError* error);
   bool OnTransportInfoMessage(const SessionMessage& msg, MessageError* error);
   bool OnTransportAcceptMessage(const SessionMessage& msg, MessageError* error);
+  bool OnRedirectError(const SessionRedirect& redirect, SessionError* error);
 
   // Verifies that we are in the appropriate state to receive this message.
   bool CheckState(State state, MessageError* error);
diff --git a/talk/p2p/base/sessionmessages.cc b/talk/p2p/base/sessionmessages.cc
index e65d826..cfd035d 100644
--- a/talk/p2p/base/sessionmessages.cc
+++ b/talk/p2p/base/sessionmessages.cc
@@ -820,4 +820,34 @@
   }
 }
 
+bool GetUriTarget(const std::string& prefix, const std::string& str,
+                  std::string* after) {
+  size_t pos = str.find(prefix);
+  if (pos == std::string::npos)
+    return false;
+
+  *after = str.substr(pos + prefix.size(), std::string::npos);
+  return true;
+}
+
+bool FindSessionRedirect(const buzz::XmlElement* stanza,
+                         SessionRedirect* redirect) {
+  const buzz::XmlElement* error_elem = GetXmlChild(stanza, LN_ERROR);
+  if (error_elem == NULL)
+    return false;
+
+  const buzz::XmlElement* redirect_elem =
+      error_elem->FirstNamed(QN_GINGLE_REDIRECT);
+  if (redirect_elem == NULL)
+    redirect_elem = error_elem->FirstNamed(buzz::QN_STANZA_REDIRECT);
+  if (redirect_elem == NULL)
+    return false;
+
+  if (!GetUriTarget(STR_REDIRECT_PREFIX, redirect_elem->BodyText(),
+                    &redirect->target))
+    return false;
+
+  return true;
+}
+
 }  // namespace cricket
diff --git a/talk/p2p/base/sessionmessages.h b/talk/p2p/base/sessionmessages.h
index ff8641c..eee8452 100644
--- a/talk/p2p/base/sessionmessages.h
+++ b/talk/p2p/base/sessionmessages.h
@@ -149,6 +149,10 @@
   std::string debug_reason;
 };
 
+struct SessionRedirect {
+  std::string target;
+};
+
 bool IsSessionMessage(const buzz::XmlElement* stanza);
 bool ParseSessionMessage(const buzz::XmlElement* stanza,
                          SessionMessage* msg,
@@ -208,6 +212,9 @@
                          const TransportParserMap& trans_parsers,
                          XmlElements* elems,
                          WriteError* error);
+// Handles both Gingle and Jingle syntax.
+bool FindSessionRedirect(const buzz::XmlElement* stanza,
+                         SessionRedirect* redirect);
 }  // namespace cricket
 
 #endif  // TALK_P2P_BASE_SESSIONMESSAGES_H_
diff --git a/talk/session/phone/call.cc b/talk/session/phone/call.cc
index fbd7a85..d72f913 100644
--- a/talk/session/phone/call.cc
+++ b/talk/session/phone/call.cc
@@ -30,6 +30,7 @@
 #include "talk/base/logging.h"
 #include "talk/base/thread.h"
 #include "talk/session/phone/call.h"
+#include "talk/session/phone/mediasessionclient.h"
 
 namespace cricket {
 
@@ -45,10 +46,13 @@
 const int kMediaMonitorInterval = 1000*15;
 }
 
-Call::Call(MediaSessionClient *session_client, bool video, bool mux)
-    : id_(talk_base::CreateRandomId()), session_client_(session_client),
-      local_renderer_(NULL), video_(video), mux_(mux),
-      muted_(false), send_to_voicemail_(true), playing_dtmf_(false) {
+Call::Call(MediaSessionClient* session_client)
+    : id_(talk_base::CreateRandomId()),
+      session_client_(session_client),
+      local_renderer_(NULL),
+      muted_(false),
+      send_to_voicemail_(true),
+      playing_dtmf_(false) {
 }
 
 Call::~Call() {
@@ -60,8 +64,9 @@
   talk_base::Thread::Current()->Clear(this);
 }
 
-Session *Call::InitiateSession(const buzz::Jid &jid) {
-  const SessionDescription* offer = session_client_->CreateOffer(video_, mux_);
+Session *Call::InitiateSession(const buzz::Jid &jid,
+                               const CallOptions& options) {
+  const SessionDescription* offer = session_client_->CreateOffer(options);
 
   Session *session = session_client_->CreateSession(this);
   AddSession(session, offer);
@@ -196,6 +201,9 @@
   VideoChannel *video_channel = NULL;
 
   const ContentInfo* audio_offer = GetFirstAudioContent(offer);
+  const ContentInfo* video_offer = GetFirstVideoContent(offer);
+  video_ = (video_offer != NULL);
+
   ASSERT(audio_offer != NULL);
   // Create voice channel and start a media monitor
   voice_channel = session_client_->channel_manager()->CreateVoiceChannel(
@@ -212,8 +220,6 @@
 
   // If desired, create video channel and start a media monitor
   if (video_ && succeeded) {
-    const ContentInfo* video_offer = GetFirstVideoContent(offer);
-    ASSERT(video_offer != NULL);
     video_channel = session_client_->channel_manager()->CreateVideoChannel(
         session, video_offer->name, true, voice_channel);
     // video_channel can be NULL in case of NullVideoEngine.
diff --git a/talk/session/phone/call.h b/talk/session/phone/call.h
index 8500fc6..f3fcbf1 100644
--- a/talk/session/phone/call.h
+++ b/talk/session/phone/call.h
@@ -36,21 +36,20 @@
 #include "talk/p2p/base/session.h"
 #include "talk/p2p/client/socketmonitor.h"
 #include "talk/xmpp/jid.h"
-#include "talk/session/phone/mediasessionclient.h"
-#include "talk/session/phone/voicechannel.h"
 #include "talk/session/phone/audiomonitor.h"
+#include "talk/session/phone/voicechannel.h"
 
 namespace cricket {
 
 class MediaSessionClient;
+struct CallOptions;
 
 class Call : public talk_base::MessageHandler, public sigslot::has_slots<> {
  public:
-  Call(MediaSessionClient *session_client,
-       bool video = false, bool mux = false);
+  Call(MediaSessionClient* session_client);
   ~Call();
 
-  Session *InitiateSession(const buzz::Jid &jid);
+  Session *InitiateSession(const buzz::Jid &jid, const CallOptions& options);
   void AcceptSession(BaseSession *session);
   void RejectSession(BaseSession *session);
   void TerminateSession(BaseSession *session);
@@ -127,7 +126,6 @@
   std::map<std::string, VideoChannel *> video_channel_map_;
   VideoRenderer* local_renderer_;
   bool video_;
-  bool mux_;
   bool muted_;
   bool send_to_voicemail_;
 
diff --git a/talk/session/phone/channel.cc b/talk/session/phone/channel.cc
index 526dbec..76c1cb4 100644
--- a/talk/session/phone/channel.cc
+++ b/talk/session/phone/channel.cc
@@ -942,8 +942,9 @@
   }
   // Set video bandwidth parameters.
   if (ret) {
-    ret = media_channel()->SetSendBandwidth(video->auto_bandwidth(),
-                                            video->bandwidth_bps());
+    int bandwidth_bps = video->bandwidth();
+    bool auto_bandwidth = (bandwidth_bps == kAutoBandwidth);
+    ret = media_channel()->SetSendBandwidth(auto_bandwidth, bandwidth_bps);
   }
   if (ret) {
     ret = media_channel()->SetSendCodecs(video->codecs());
diff --git a/talk/session/phone/mediachannel.h b/talk/session/phone/mediachannel.h
index 2650ea7..01bd8b5 100644
--- a/talk/session/phone/mediachannel.h
+++ b/talk/session/phone/mediachannel.h
@@ -1,6 +1,6 @@
 /*
  * libjingle
- * Copyright 2004--2007, Google Inc.
+ * Copyright 2004--2010, Google Inc.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -237,7 +237,7 @@
 
   // Size of an I420 image of given dimensions when stored as a frame buffer.
   static size_t SizeOf(size_t w, size_t h) {
-    return w * h * 3 / 2;
+    return w * h + ((w + 1) / 2) * ((h + 1) / 2) * 2;
   }
 
  protected:
diff --git a/talk/session/phone/mediasessionclient.cc b/talk/session/phone/mediasessionclient.cc
index 6e9d8b6..645524a 100644
--- a/talk/session/phone/mediasessionclient.cc
+++ b/talk/session/phone/mediasessionclient.cc
@@ -32,6 +32,7 @@
 #include "talk/base/helpers.h"
 #include "talk/base/logging.h"
 #include "talk/base/stringutils.h"
+#include "talk/base/stringencode.h"
 #include "talk/p2p/base/constants.h"
 #include "talk/p2p/base/parsing.h"
 #include "talk/session/phone/cryptoparams.h"
@@ -133,7 +134,8 @@
 #endif
 }
 
-SessionDescription* MediaSessionClient::CreateOffer(bool video, bool set_ssrc) {
+SessionDescription* MediaSessionClient::CreateOffer(
+    const CallOptions& options) {
   SessionDescription* offer = new SessionDescription();
   AudioContentDescription* audio = new AudioContentDescription();
 
@@ -144,7 +146,7 @@
        codec != audio_codecs.end(); ++codec) {
     audio->AddCodec(*codec);
   }
-  if (set_ssrc) {
+  if (options.is_muc) {
     audio->set_ssrc(0);
   }
   audio->SortCodecs();
@@ -168,7 +170,7 @@
   offer->AddContent(CN_AUDIO, NS_JINGLE_RTP, audio);
 
   // add video codecs, if this is a video call
-  if (video) {
+  if (options.is_video) {
     VideoContentDescription* video = new VideoContentDescription();
     VideoCodecs video_codecs;
     channel_manager_->GetSupportedVideoCodecs(&video_codecs);
@@ -176,9 +178,10 @@
          codec != video_codecs.end(); ++codec) {
       video->AddCodec(*codec);
     }
-    if (set_ssrc) {
+    if (options.is_muc) {
       video->set_ssrc(0);
     }
+    video->set_bandwidth(options.video_bandwidth);
     video->SortCodecs();
 
     if (secure() != SEC_DISABLED) {
@@ -329,8 +332,8 @@
   return accept;
 }
 
-Call *MediaSessionClient::CreateCall(bool video, bool mux) {
-  Call *call = new Call(this, video, mux);
+Call *MediaSessionClient::CreateCall() {
+  Call *call = new Call(this);
   calls_[call->id()] = call;
   SignalCallCreate(call);
   return call;
@@ -359,13 +362,12 @@
     const SessionDescription* offer = session->remote_description();
     const SessionDescription* accept = CreateAnswer(offer);
     const ContentInfo* audio_content = GetFirstAudioContent(accept);
-    const ContentInfo* video_content = GetFirstVideoContent(accept);
     const AudioContentDescription* audio_accept = (!audio_content) ? NULL :
         static_cast<const AudioContentDescription*>(audio_content->description);
 
     // For some reason, we need to create the call even when we
     // reject.
-    Call *call = CreateCall(video_content != NULL);
+    Call *call = CreateCall();
     session_map_[session->id()] = call;
     call->IncomingSession(session, offer);
 
@@ -526,6 +528,17 @@
   return true;
 }
 
+void ParseBandwidth(const buzz::XmlElement* parent_elem,
+                    MediaContentDescription* media) {
+  const buzz::XmlElement* bw_elem = GetXmlChild(parent_elem, LN_BANDWIDTH);
+  int bandwidth_kbps;
+  if (bw_elem && FromString(bw_elem->BodyText(), &bandwidth_kbps)) {
+    if (bandwidth_kbps >= 0) {
+      media->set_bandwidth(bandwidth_kbps * 1000);
+    }
+  }
+}
+
 bool ParseGingleAudioContent(const buzz::XmlElement* content_elem,
                              const ContentDescription** content,
                              ParseError* error) {
@@ -575,6 +588,7 @@
   }
 
   ParseGingleSsrc(content_elem, QN_GINGLE_VIDEO_SRCID, video);
+  ParseBandwidth(content_elem, video);
 
   if (!ParseGingleEncryption(content_elem, QN_GINGLE_VIDEO_CRYPTO_USAGE,
                              video, error)) {
@@ -705,6 +719,8 @@
     }
   }
 
+  ParseBandwidth(content_elem, video);
+
   if (!ParseJingleEncryption(content_elem, video, error)) {
     return false;
   }
@@ -774,6 +790,15 @@
   return elem;
 }
 
+buzz::XmlElement* CreateBandwidthElem(int bps) {
+  int kbps = bps / 1000;
+  buzz::XmlElement* elem = new buzz::XmlElement(
+      buzz::QName(true, "", LN_BANDWIDTH), true);
+  elem->AddAttr(buzz::QN_TYPE, "AS");
+  SetXmlBody(elem, kbps);
+  return elem;
+}
+
 // For Jingle, usage_qname is empty.
 buzz::XmlElement* CreateJingleEncryptionElem(const CryptoParamsVec& cryptos,
                                              bool required) {
@@ -815,7 +840,6 @@
   return encryption_elem;
 }
 
-
 buzz::XmlElement* CreateGingleAudioContentElem(
     const AudioContentDescription* audio,
     bool crypto_required) {
@@ -856,6 +880,9 @@
     elem->AddElement(CreateGingleSsrcElem(
         QN_GINGLE_VIDEO_SRCID, video->ssrc()));
   }
+  if (video->bandwidth() != kAutoBandwidth) {
+    elem->AddElement(CreateBandwidthElem(video->bandwidth()));
+  }
 
   const CryptoParamsVec& cryptos = video->cryptos();
   if (!cryptos.empty()) {
@@ -949,6 +976,10 @@
     elem->AddElement(CreateJingleEncryptionElem(cryptos, crypto_required));
   }
 
+  if (video->bandwidth() != kAutoBandwidth) {
+    elem->AddElement(CreateBandwidthElem(video->bandwidth()));
+  }
+
   // TODO: Figure out how to integrate SSRC into Jingle.
   return elem;
 }
diff --git a/talk/session/phone/mediasessionclient.h b/talk/session/phone/mediasessionclient.h
index 6534259..8f157ee 100644
--- a/talk/session/phone/mediasessionclient.h
+++ b/talk/session/phone/mediasessionclient.h
@@ -65,6 +65,21 @@
 //               in the offer.)
 enum SecureMediaPolicy {SEC_DISABLED, SEC_ENABLED, SEC_REQUIRED};
 
+const int kAutoBandwidth = -1;
+
+struct CallOptions {
+  CallOptions() :
+      is_video(false),
+      is_muc(false),
+      video_bandwidth(kAutoBandwidth) {
+  }
+
+  bool is_video;
+  bool is_muc;
+  // bps. -1 == auto.
+  int video_bandwidth;
+};
+
 class MediaSessionClient: public SessionClient, public sigslot::has_slots<> {
  public:
 
@@ -81,7 +96,7 @@
 
   int GetCapabilities() { return channel_manager_->GetCapabilities(); }
 
-  Call *CreateCall(bool video = false, bool mux = false);
+  Call *CreateCall();
   void DestroyCall(Call *call);
 
   Call *GetFocus();
@@ -115,7 +130,7 @@
   sigslot::signal1<Call *> SignalCallDestroy;
   sigslot::repeater0<> SignalDevicesChange;
 
-  SessionDescription* CreateOffer(bool video = false, bool set_ssrc = false);
+  SessionDescription* CreateOffer(const CallOptions& options);
   SessionDescription* CreateAnswer(const SessionDescription* offer);
 
   SecureMediaPolicy secure() const { return secure_; }
@@ -153,10 +168,14 @@
 
 class MediaContentDescription : public ContentDescription {
  public:
-  MediaContentDescription() : ssrc_(0), bandwidth_bps_(-1),
-                              ssrc_set_(false), auto_bandwidth_(true),
-                              rtcp_mux_(false), rtp_headers_disabled_(false),
-                              crypto_required_(false) {}
+  MediaContentDescription()
+      : ssrc_(0),
+        ssrc_set_(false),
+        rtcp_mux_(false), 
+        rtp_headers_disabled_(false),
+        crypto_required_(false),
+        bandwidth_(kAutoBandwidth) {
+  }
 
   virtual MediaType type() const = 0;
 
@@ -186,21 +205,17 @@
     crypto_required_ = crypto;
   }
 
-  int bandwidth_bps() const { return bandwidth_bps_; }
-  void set_bandwidth_bps(int bps) { bandwidth_bps_ = bps; }
-
-  bool auto_bandwidth() const { return auto_bandwidth_; }
-  void set_auto_bandwidth(bool enable) { auto_bandwidth_ = enable; }
+  int bandwidth() const { return bandwidth_; }
+  void set_bandwidth(int bandwidth) { bandwidth_ = bandwidth; }
 
  protected:
   uint32 ssrc_;
-  int bandwidth_bps_;    // fixed or max video bandwidth
   bool ssrc_set_;
-  bool auto_bandwidth_;    // if true, bandwidth_bps_ < 0 flags default limits
   bool rtcp_mux_;
   bool rtp_headers_disabled_;
   std::vector<CryptoParams> cryptos_;
   bool crypto_required_;
+  int bandwidth_;
 };
 
 template <class C>