Make the media content send only if offerToReceive is false while local streams exist.
We previously do not add the media content if offerToReceive is false.

BUG=3833
R=pthatcher@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/26609004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@7390 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/app/webrtc/mediastreamsignaling.cc b/talk/app/webrtc/mediastreamsignaling.cc
index df51ba1..9b4d2a4 100644
--- a/talk/app/webrtc/mediastreamsignaling.cc
+++ b/talk/app/webrtc/mediastreamsignaling.cc
@@ -57,32 +57,20 @@
   bool value;
   size_t mandatory_constraints_satisfied = 0;
 
-  if (FindConstraint(constraints,
-                     MediaConstraintsInterface::kOfferToReceiveAudio,
-                     &value, &mandatory_constraints_satisfied)) {
-    // |options-|has_audio| can only change from false to
-    // true, but never change from true to false. This is to make sure
-    // CreateOffer / CreateAnswer doesn't remove a media content
-    // description that has been created.
-    options->has_audio |= value;
-  } else {
-    // kOfferToReceiveAudio defaults to true according to spec.
-    options->has_audio = true;
+  // kOfferToReceiveAudio defaults to true according to spec.
+  if (!FindConstraint(constraints,
+                      MediaConstraintsInterface::kOfferToReceiveAudio,
+                      &value, &mandatory_constraints_satisfied) || value) {
+    options->recv_audio = true;
   }
 
-  if (FindConstraint(constraints,
-                     MediaConstraintsInterface::kOfferToReceiveVideo,
-                     &value, &mandatory_constraints_satisfied)) {
-    // |options->has_video| can only change from false to
-    // true, but never change from true to false. This is to make sure
-    // CreateOffer / CreateAnswer doesn't remove a media content
-    // description that has been created.
-    options->has_video |= value;
-  } else {
-    // kOfferToReceiveVideo defaults to false according to spec. But
-    // if it is an answer and video is offered, we should still accept video
-    // per default.
-    options->has_video = true;
+  // kOfferToReceiveVideo defaults to false according to spec. But
+  // if it is an answer and video is offered, we should still accept video
+  // per default.
+  if (!FindConstraint(constraints,
+                      MediaConstraintsInterface::kOfferToReceiveVideo,
+                      &value, &mandatory_constraints_satisfied) || value) {
+    options->recv_video = true;
   }
 
   if (FindConstraint(constraints,
@@ -120,7 +108,7 @@
 // and the constraint kUseRtpMux has not disabled bundle.
 static bool EvaluateNeedForBundle(const cricket::MediaSessionOptions& options) {
   return options.bundle_enabled &&
-      (options.has_audio || options.has_video || options.has_data());
+      (options.has_audio() || options.has_video() || options.has_data());
 }
 
 static bool MediaContentDirectionHasSend(cricket::MediaContentDirection dir) {
@@ -148,7 +136,7 @@
       // For each audio track in the stream, add it to the MediaSessionOptions.
       for (size_t j = 0; j < audio_tracks.size(); ++j) {
         scoped_refptr<MediaStreamTrackInterface> track(audio_tracks[j]);
-        session_options->AddStream(
+        session_options->AddSendStream(
             cricket::MEDIA_TYPE_AUDIO, track->id(), stream->label());
       }
 
@@ -157,7 +145,7 @@
       // For each video track in the stream, add it to the MediaSessionOptions.
       for (size_t j = 0; j < video_tracks.size(); ++j) {
         scoped_refptr<MediaStreamTrackInterface> track(video_tracks[j]);
-        session_options->AddStream(
+        session_options->AddSendStream(
             cricket::MEDIA_TYPE_VIDEO, track->id(), stream->label());
       }
     }
@@ -176,7 +164,7 @@
       // track label is the same as |streamid|.
       const std::string& streamid = channel->label();
       const std::string& sync_label = channel->label();
-      session_options->AddStream(
+      session_options->AddSendStream(
           cricket::MEDIA_TYPE_DATA, streamid, sync_label);
     }
   }
@@ -425,17 +413,21 @@
     return false;
   }
 
-  session_options->has_audio = false;
-  session_options->has_video = false;
   SetStreams(session_options, local_streams_, rtp_data_channels_);
 
-  // If |offer_to_receive_[audio/video]| is undefined, respect the flags set
-  // from SetStreams. Otherwise, overwrite it based on |rtc_options|.
-  if (rtc_options.offer_to_receive_audio != RTCOfferAnswerOptions::kUndefined) {
-    session_options->has_audio = rtc_options.offer_to_receive_audio > 0;
+  // According to the spec, offer to receive audio/video if the constraint is
+  // not set and there are send streams.
+  if (rtc_options.offer_to_receive_audio == RTCOfferAnswerOptions::kUndefined) {
+    session_options->recv_audio =
+        session_options->HasSendMediaStream(cricket::MEDIA_TYPE_AUDIO);
+  } else {
+    session_options->recv_audio = (rtc_options.offer_to_receive_audio > 0);
   }
-  if (rtc_options.offer_to_receive_video != RTCOfferAnswerOptions::kUndefined) {
-    session_options->has_video = rtc_options.offer_to_receive_video > 0;
+  if (rtc_options.offer_to_receive_video == RTCOfferAnswerOptions::kUndefined) {
+    session_options->recv_video =
+        session_options->HasSendMediaStream(cricket::MEDIA_TYPE_VIDEO);
+  } else {
+    session_options->recv_video = (rtc_options.offer_to_receive_video > 0);
   }
 
   session_options->vad_enabled = rtc_options.voice_activity_detection;
@@ -449,10 +441,10 @@
 bool MediaStreamSignaling::GetOptionsForAnswer(
     const MediaConstraintsInterface* constraints,
     cricket::MediaSessionOptions* options) {
-  options->has_audio = false;
-  options->has_video = false;
   SetStreams(options, local_streams_, rtp_data_channels_);
 
+  options->recv_audio = false;
+  options->recv_video = false;
   if (!ParseConstraintsForAnswer(constraints, options)) {
     return false;
   }
diff --git a/talk/app/webrtc/mediastreamsignaling_unittest.cc b/talk/app/webrtc/mediastreamsignaling_unittest.cc
index 84f67b9..038c67d 100644
--- a/talk/app/webrtc/mediastreamsignaling_unittest.cc
+++ b/talk/app/webrtc/mediastreamsignaling_unittest.cc
@@ -216,7 +216,7 @@
       webrtc::AudioTrackInterface* audio = audio_tracks[j];
       EXPECT_EQ(options.streams[stream_index].sync_label, stream->label());
       EXPECT_EQ(options.streams[stream_index++].id, audio->id());
-      EXPECT_TRUE(options.has_audio);
+      EXPECT_TRUE(options.has_audio());
     }
     VideoTrackVector video_tracks = stream->GetVideoTracks();
     ASSERT_GE(options.streams.size(), stream_index + video_tracks.size());
@@ -224,7 +224,7 @@
       webrtc::VideoTrackInterface* video = video_tracks[j];
       EXPECT_EQ(options.streams[stream_index].sync_label, stream->label());
       EXPECT_EQ(options.streams[stream_index++].id, video->id());
-      EXPECT_TRUE(options.has_video);
+      EXPECT_TRUE(options.has_video());
     }
   }
 }
@@ -634,8 +634,8 @@
 
   cricket::MediaSessionOptions options;
   EXPECT_TRUE(signaling_->GetOptionsForOffer(rtc_options, &options));
-  EXPECT_TRUE(options.has_audio);
-  EXPECT_TRUE(options.has_video);
+  EXPECT_TRUE(options.has_audio());
+  EXPECT_TRUE(options.has_video());
   EXPECT_TRUE(options.bundle_enabled);
 }
 
@@ -647,8 +647,8 @@
 
   cricket::MediaSessionOptions options;
   EXPECT_TRUE(signaling_->GetOptionsForOffer(rtc_options, &options));
-  EXPECT_TRUE(options.has_audio);
-  EXPECT_FALSE(options.has_video);
+  EXPECT_TRUE(options.has_audio());
+  EXPECT_FALSE(options.has_video());
   EXPECT_TRUE(options.bundle_enabled);
 }
 
@@ -659,8 +659,8 @@
 
   cricket::MediaSessionOptions options;
   EXPECT_TRUE(signaling_->GetOptionsForOffer(rtc_options, &options));
-  EXPECT_FALSE(options.has_audio);
-  EXPECT_FALSE(options.has_video);
+  EXPECT_FALSE(options.has_audio());
+  EXPECT_FALSE(options.has_video());
   EXPECT_FALSE(options.bundle_enabled);
   EXPECT_TRUE(options.vad_enabled);
   EXPECT_FALSE(options.transport_options.ice_restart);
@@ -675,8 +675,8 @@
 
   cricket::MediaSessionOptions options;
   EXPECT_TRUE(signaling_->GetOptionsForOffer(rtc_options, &options));
-  EXPECT_FALSE(options.has_audio);
-  EXPECT_TRUE(options.has_video);
+  EXPECT_FALSE(options.has_audio());
+  EXPECT_TRUE(options.has_video());
   EXPECT_TRUE(options.bundle_enabled);
 }
 
@@ -691,8 +691,8 @@
 
   cricket::MediaSessionOptions options;
   EXPECT_TRUE(signaling_->GetOptionsForOffer(rtc_options, &options));
-  EXPECT_TRUE(options.has_audio);
-  EXPECT_TRUE(options.has_video);
+  EXPECT_TRUE(options.has_audio());
+  EXPECT_TRUE(options.has_video());
   EXPECT_FALSE(options.bundle_enabled);
 }
 
@@ -743,16 +743,16 @@
 
   cricket::MediaSessionOptions answer_options;
   EXPECT_TRUE(signaling_->GetOptionsForAnswer(&answer_c, &answer_options));
-  EXPECT_TRUE(answer_options.has_audio);
-  EXPECT_TRUE(answer_options.has_video);
+  EXPECT_TRUE(answer_options.has_audio());
+  EXPECT_TRUE(answer_options.has_video());
 
   RTCOfferAnswerOptions rtc_offer_optoins;
 
   cricket::MediaSessionOptions offer_options;
   EXPECT_TRUE(
       signaling_->GetOptionsForOffer(rtc_offer_optoins, &offer_options));
-  EXPECT_FALSE(offer_options.has_audio);
-  EXPECT_FALSE(offer_options.has_video);
+  EXPECT_FALSE(offer_options.has_audio());
+  EXPECT_FALSE(offer_options.has_video());
 
   RTCOfferAnswerOptions updated_rtc_offer_optoins;
   updated_rtc_offer_optoins.offer_to_receive_audio = 1;
@@ -761,8 +761,8 @@
   cricket::MediaSessionOptions updated_offer_options;
   EXPECT_TRUE(signaling_->GetOptionsForOffer(updated_rtc_offer_optoins,
                                              &updated_offer_options));
-  EXPECT_TRUE(updated_offer_options.has_audio);
-  EXPECT_TRUE(updated_offer_options.has_video);
+  EXPECT_TRUE(updated_offer_options.has_audio());
+  EXPECT_TRUE(updated_offer_options.has_video());
 
   // Since an offer has been created with both audio and video, subsequent
   // offers and answers should contain both audio and video.
@@ -776,16 +776,16 @@
   cricket::MediaSessionOptions updated_answer_options;
   EXPECT_TRUE(signaling_->GetOptionsForAnswer(&updated_answer_c,
                                               &updated_answer_options));
-  EXPECT_TRUE(updated_answer_options.has_audio);
-  EXPECT_TRUE(updated_answer_options.has_video);
+  EXPECT_TRUE(updated_answer_options.has_audio());
+  EXPECT_TRUE(updated_answer_options.has_video());
 
   RTCOfferAnswerOptions default_rtc_options;
   EXPECT_TRUE(signaling_->GetOptionsForOffer(default_rtc_options,
                                              &updated_offer_options));
   // By default, |has_audio| or |has_video| are false if there is no media
   // track.
-  EXPECT_FALSE(updated_offer_options.has_audio);
-  EXPECT_FALSE(updated_offer_options.has_video);
+  EXPECT_FALSE(updated_offer_options.has_audio());
+  EXPECT_FALSE(updated_offer_options.has_video());
 }
 
 // This test verifies that the remote MediaStreams corresponding to a received
diff --git a/talk/app/webrtc/webrtcsession_unittest.cc b/talk/app/webrtc/webrtcsession_unittest.cc
index 29a7b26..d3480a0 100644
--- a/talk/app/webrtc/webrtcsession_unittest.cc
+++ b/talk/app/webrtc/webrtcsession_unittest.cc
@@ -507,7 +507,7 @@
   void VerifyAnswerFromNonCryptoOffer() {
     // Create an SDP without Crypto.
     cricket::MediaSessionOptions options;
-    options.has_video = true;
+    options.recv_video = true;
     JsepSessionDescription* offer(
         CreateRemoteOffer(options, cricket::SEC_DISABLED));
     ASSERT_TRUE(offer != NULL);
@@ -521,7 +521,7 @@
 
   void VerifyAnswerFromCryptoOffer() {
     cricket::MediaSessionOptions options;
-    options.has_video = true;
+    options.recv_video = true;
     options.bundle_enabled = true;
     scoped_ptr<JsepSessionDescription> offer(
         CreateRemoteOffer(options, cricket::SEC_REQUIRED));
@@ -713,7 +713,7 @@
       SessionDescriptionInterface** nocrypto_answer) {
     // Create a SDP without Crypto.
     cricket::MediaSessionOptions options;
-    options.has_video = true;
+    options.recv_video = true;
     options.bundle_enabled = true;
     *offer = CreateRemoteOffer(options, cricket::SEC_ENABLED);
     ASSERT_TRUE(*offer != NULL);
@@ -727,7 +727,7 @@
   void CreateDtlsOfferAndNonDtlsAnswer(SessionDescriptionInterface** offer,
       SessionDescriptionInterface** nodtls_answer) {
     cricket::MediaSessionOptions options;
-    options.has_video = true;
+    options.recv_video = true;
     options.bundle_enabled = true;
 
     rtc::scoped_ptr<SessionDescriptionInterface> temp_offer(
@@ -789,8 +789,8 @@
       const char* sctp_stream_name, int new_port,
       cricket::MediaSessionOptions options) {
     options.data_channel_type = cricket::DCT_SCTP;
-    options.AddStream(cricket::MEDIA_TYPE_DATA, "datachannel",
-                      sctp_stream_name);
+    options.AddSendStream(cricket::MEDIA_TYPE_DATA, "datachannel",
+                          sctp_stream_name);
     return ChangeSDPSctpPort(new_port, CreateRemoteOffer(options));
   }
 
@@ -1386,7 +1386,7 @@
 TEST_F(WebRtcSessionTest, TestSetNonSdesOfferWhenSdesOn) {
   Init(NULL);
   cricket::MediaSessionOptions options;
-  options.has_video = true;
+  options.recv_video = true;
   JsepSessionDescription* offer = CreateRemoteOffer(
       options, cricket::SEC_DISABLED);
   ASSERT_TRUE(offer != NULL);
@@ -1433,7 +1433,7 @@
   InitWithDtls();
   SetFactoryDtlsSrtp();
   cricket::MediaSessionOptions options;
-  options.has_video = true;
+  options.recv_video = true;
   JsepSessionDescription* offer =
       CreateRemoteOffer(options, cricket::SEC_DISABLED);
   ASSERT_TRUE(offer != NULL);
@@ -1473,7 +1473,7 @@
   SetLocalDescriptionWithoutError(offer);
 
   cricket::MediaSessionOptions options;
-  options.has_video = true;
+  options.recv_video = true;
   JsepSessionDescription* answer =
       CreateRemoteAnswer(offer, options, cricket::SEC_DISABLED);
   ASSERT_TRUE(answer != NULL);
@@ -1490,7 +1490,7 @@
   MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp);
   InitWithDtls();
   cricket::MediaSessionOptions options;
-  options.has_video = true;
+  options.recv_video = true;
   options.bundle_enabled = true;
   JsepSessionDescription* offer = CreateRemoteOffer(
       options, cricket::SEC_REQUIRED);
@@ -1531,7 +1531,7 @@
   InitWithDtls();
   SessionDescriptionInterface* offer = CreateOffer();
   cricket::MediaSessionOptions options;
-  options.has_video = true;
+  options.recv_video = true;
   JsepSessionDescription* answer =
       CreateRemoteAnswer(offer, options, cricket::SEC_ENABLED);
 
@@ -1560,7 +1560,7 @@
   SetLocalDescriptionWithoutError(offer);
 
   cricket::MediaSessionOptions options;
-  options.has_video = true;
+  options.recv_video = true;
   JsepSessionDescription* answer =
       CreateRemoteAnswer(offer, options, cricket::SEC_DISABLED);
   ASSERT_TRUE(answer != NULL);
@@ -1578,7 +1578,7 @@
   InitWithDtls();
 
   cricket::MediaSessionOptions options;
-  options.has_video = true;
+  options.recv_video = true;
   JsepSessionDescription* offer =
       CreateRemoteOffer(options, cricket::SEC_DISABLED);
   ASSERT_TRUE(offer != NULL);
@@ -2290,8 +2290,8 @@
   SessionDescriptionInterface* offer = CreateOffer();
 
   cricket::MediaSessionOptions options;
-  options.has_audio = false;
-  options.has_video = true;
+  options.recv_audio = false;
+  options.recv_video = true;
   SessionDescriptionInterface* answer = CreateRemoteAnswer(
       offer, options, cricket::SEC_ENABLED);
 
@@ -2946,7 +2946,7 @@
 TEST_F(WebRtcSessionTest, TestCreateAnswerWithNewUfragAndPassword) {
   Init(NULL);
   cricket::MediaSessionOptions options;
-  options.has_video = true;
+  options.recv_video = true;
   rtc::scoped_ptr<JsepSessionDescription> offer(
       CreateRemoteOffer(options));
   SetRemoteDescriptionWithoutError(offer.release());
@@ -2977,7 +2977,7 @@
 TEST_F(WebRtcSessionTest, TestCreateAnswerWithOldUfragAndPassword) {
   Init(NULL);
   cricket::MediaSessionOptions options;
-  options.has_video = true;
+  options.recv_video = true;
   rtc::scoped_ptr<JsepSessionDescription> offer(
       CreateRemoteOffer(options));
   SetRemoteDescriptionWithoutError(offer.release());
@@ -3042,7 +3042,7 @@
 TEST_F(WebRtcSessionTest, SetSdpFailedOnSessionError) {
   Init(NULL);
   cricket::MediaSessionOptions options;
-  options.has_video = true;
+  options.recv_video = true;
 
   cricket::BaseSession::Error error_code = cricket::BaseSession::ERROR_CONTENT;
   std::string error_code_str = "ERROR_CONTENT";
@@ -3219,7 +3219,7 @@
   SetFactoryDtlsSrtp();
 
   cricket::MediaSessionOptions options;
-  options.has_video = true;
+  options.recv_video = true;
   scoped_ptr<JsepSessionDescription> offer(
         CreateRemoteOffer(options, cricket::SEC_DISABLED));
   ASSERT_TRUE(offer.get() != NULL);
@@ -3402,7 +3402,7 @@
   SetRemoteDescriptionWithoutError(answer);
 
   cricket::MediaSessionOptions options;
-  options.has_video = true;
+  options.recv_video = true;
   offer = CreateRemoteOffer(options, cricket::SEC_DISABLED);
 
   cricket::Candidate candidate1;
@@ -3432,7 +3432,7 @@
   SetRemoteDescriptionWithoutError(answer);
 
   cricket::MediaSessionOptions options;
-  options.has_video = true;
+  options.recv_video = true;
   offer = CreateRemoteOffer(options, cricket::SEC_DISABLED);
   SetRemoteDescriptionWithoutError(offer);
 
diff --git a/talk/session/media/mediasession.cc b/talk/session/media/mediasession.cc
index 92dd257..54eeffa 100644
--- a/talk/session/media/mediasession.cc
+++ b/talk/session/media/mediasession.cc
@@ -1075,37 +1075,33 @@
   return type_str;
 }
 
-void MediaSessionOptions::AddStream(MediaType type,
+void MediaSessionOptions::AddSendStream(MediaType type,
                                     const std::string& id,
                                     const std::string& sync_label) {
-  AddStreamInternal(type, id, sync_label, 1);
+  AddSendStreamInternal(type, id, sync_label, 1);
 }
 
-void MediaSessionOptions::AddVideoStream(
+void MediaSessionOptions::AddSendVideoStream(
     const std::string& id,
     const std::string& sync_label,
     int num_sim_layers) {
-  AddStreamInternal(MEDIA_TYPE_VIDEO, id, sync_label, num_sim_layers);
+  AddSendStreamInternal(MEDIA_TYPE_VIDEO, id, sync_label, num_sim_layers);
 }
 
-void MediaSessionOptions::AddStreamInternal(
+void MediaSessionOptions::AddSendStreamInternal(
     MediaType type,
     const std::string& id,
     const std::string& sync_label,
     int num_sim_layers) {
   streams.push_back(Stream(type, id, sync_label, num_sim_layers));
 
-  if (type == MEDIA_TYPE_VIDEO)
-    has_video = true;
-  else if (type == MEDIA_TYPE_AUDIO)
-    has_audio = true;
   // If we haven't already set the data_channel_type, and we add a
   // stream, we assume it's an RTP data stream.
-  else if (type == MEDIA_TYPE_DATA && data_channel_type == DCT_NONE)
+  if (type == MEDIA_TYPE_DATA && data_channel_type == DCT_NONE)
     data_channel_type = DCT_RTP;
 }
 
-void MediaSessionOptions::RemoveStream(MediaType type,
+void MediaSessionOptions::RemoveSendStream(MediaType type,
                                        const std::string& id) {
   Streams::iterator stream_it = streams.begin();
   for (; stream_it != streams.end(); ++stream_it) {
@@ -1117,6 +1113,16 @@
   ASSERT(false);
 }
 
+bool MediaSessionOptions::HasSendMediaStream(MediaType type) const {
+  Streams::const_iterator stream_it = streams.begin();
+  for (; stream_it != streams.end(); ++stream_it) {
+    if (stream_it->type == type) {
+      return true;
+    }
+  }
+  return false;
+}
+
 MediaSessionDescriptionFactory::MediaSessionDescriptionFactory(
     const TransportDescriptionFactory* transport_desc_factory)
     : secure_(SEC_DISABLED),
@@ -1195,14 +1201,15 @@
       }
     }
   }
+
   // Append contents that are not in |current_description|.
-  if (!audio_added && options.has_audio &&
+  if (!audio_added && options.has_audio() &&
       !AddAudioContentForOffer(options, current_description,
                                audio_rtp_extensions, audio_codecs,
                                &current_streams, offer.get())) {
     return NULL;
   }
-  if (!video_added && options.has_video &&
+  if (!video_added && options.has_video() &&
       !AddVideoContentForOffer(options, current_description,
                                video_rtp_extensions, video_codecs,
                                &current_streams, offer.get())) {
@@ -1460,6 +1467,10 @@
   bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
   SetMediaProtocol(secure_transport, audio.get());
 
+  if (!options.recv_audio) {
+    audio->set_direction(MD_SENDONLY);
+  }
+
   desc->AddContent(CN_AUDIO, NS_JINGLE_RTP, audio.release());
   if (!AddTransportOffer(CN_AUDIO, options.transport_options,
                          current_description, desc)) {
@@ -1500,6 +1511,11 @@
 
   bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
   SetMediaProtocol(secure_transport, video.get());
+
+  if (!options.recv_video) {
+    video->set_direction(MD_SENDONLY);
+  }
+
   desc->AddContent(CN_VIDEO, NS_JINGLE_RTP, video.release());
   if (!AddTransportOffer(CN_VIDEO, options.transport_options,
                          current_description, desc)) {
@@ -1610,7 +1626,7 @@
     return false;  // Fails the session setup.
   }
 
-  bool rejected = !options.has_audio || audio_content->rejected ||
+  bool rejected = !options.has_audio() || audio_content->rejected ||
       !IsMediaProtocolSupported(MEDIA_TYPE_AUDIO,
                                 audio_answer->protocol(),
                                 audio_transport->secure());
@@ -1663,7 +1679,7 @@
           video_answer.get())) {
     return false;
   }
-  bool rejected = !options.has_video || video_content->rejected ||
+  bool rejected = !options.has_video() || video_content->rejected ||
       !IsMediaProtocolSupported(MEDIA_TYPE_VIDEO,
                                 video_answer->protocol(),
                                 video_transport->secure());
diff --git a/talk/session/media/mediasession.h b/talk/session/media/mediasession.h
index 93894cd..992439c 100644
--- a/talk/session/media/mediasession.h
+++ b/talk/session/media/mediasession.h
@@ -93,8 +93,8 @@
 
 struct MediaSessionOptions {
   MediaSessionOptions() :
-      has_audio(true),  // Audio enabled by default.
-      has_video(false),
+      recv_audio(true),
+      recv_video(false),
       data_channel_type(DCT_NONE),
       is_muc(false),
       vad_enabled(true),  // When disabled, removes all CN codecs from SDP.
@@ -104,28 +104,36 @@
       data_bandwidth(kDataMaxBandwidth) {
   }
 
+  bool has_audio() const {
+    return recv_audio || HasSendMediaStream(MEDIA_TYPE_AUDIO);
+  }
+  bool has_video() const {
+    return recv_video || HasSendMediaStream(MEDIA_TYPE_VIDEO);
+  }
   bool has_data() const { return data_channel_type != DCT_NONE; }
 
   // Add a stream with MediaType type and id.
   // All streams with the same sync_label will get the same CNAME.
   // All ids must be unique.
-  void AddStream(MediaType type,
+  void AddSendStream(MediaType type,
                  const std::string& id,
                  const std::string& sync_label);
-  void AddVideoStream(const std::string& id,
+  void AddSendVideoStream(const std::string& id,
                       const std::string& sync_label,
                       int num_sim_layers);
-  void RemoveStream(MediaType type, const std::string& id);
+  void RemoveSendStream(MediaType type, const std::string& id);
 
 
   // Helper function.
-  void AddStreamInternal(MediaType type,
+  void AddSendStreamInternal(MediaType type,
                          const std::string& id,
                          const std::string& sync_label,
                          int num_sim_layers);
 
-  bool has_audio;
-  bool has_video;
+  bool HasSendMediaStream(MediaType type) const;
+
+  bool recv_audio;
+  bool recv_video;
   DataChannelType data_channel_type;
   bool is_muc;
   bool vad_enabled;
diff --git a/talk/session/media/mediasession_unittest.cc b/talk/session/media/mediasession_unittest.cc
index 1b66f88..ef155f0 100644
--- a/talk/session/media/mediasession_unittest.cc
+++ b/talk/session/media/mediasession_unittest.cc
@@ -190,6 +190,13 @@
   return mdesc && mdesc->type() == media_type;
 }
 
+static cricket::MediaContentDirection
+GetMediaDirection(const ContentInfo* content) {
+  cricket::MediaContentDescription* desc =
+      reinterpret_cast<cricket::MediaContentDescription*>(content->description);
+  return desc->direction();
+}
+
 class MediaSessionDescriptionFactoryTest : public testing::Test {
  public:
   MediaSessionDescriptionFactoryTest()
@@ -280,7 +287,7 @@
     }
     ASSERT_TRUE(desc.get() != NULL);
     const TransportInfo* ti_audio = desc->GetTransportInfoByName("audio");
-    if (options.has_audio) {
+    if (options.has_audio()) {
       EXPECT_TRUE(ti_audio != NULL);
       if (has_current_desc) {
         EXPECT_EQ(current_audio_ufrag, ti_audio->description.ice_ufrag);
@@ -296,7 +303,7 @@
       EXPECT_TRUE(ti_audio == NULL);
     }
     const TransportInfo* ti_video = desc->GetTransportInfoByName("video");
-    if (options.has_video) {
+    if (options.has_video()) {
       EXPECT_TRUE(ti_video != NULL);
       if (options.bundle_enabled) {
         EXPECT_EQ(ti_audio->description.ice_ufrag,
@@ -344,8 +351,8 @@
   void TestCryptoWithBundle(bool offer) {
     f1_.set_secure(SEC_ENABLED);
     MediaSessionOptions options;
-    options.has_audio = true;
-    options.has_video = true;
+    options.recv_audio = true;
+    options.recv_video = true;
     options.data_channel_type = cricket::DCT_RTP;
     rtc::scoped_ptr<SessionDescription> ref_desc;
     rtc::scoped_ptr<SessionDescription> desc;
@@ -397,7 +404,7 @@
       cricket::MediaContentDirection direction_in_offer,
       cricket::MediaContentDirection expected_direction_in_answer) {
     MediaSessionOptions opts;
-    opts.has_video = true;
+    opts.recv_video = true;
     rtc::scoped_ptr<SessionDescription> offer(
         f1_.CreateOffer(opts, NULL));
     ASSERT_TRUE(offer.get() != NULL);
@@ -469,7 +476,7 @@
 // Create a typical video offer, and ensure it matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoOffer) {
   MediaSessionOptions opts;
-  opts.has_video = true;
+  opts.recv_video = true;
   f1_.set_secure(SEC_ENABLED);
   rtc::scoped_ptr<SessionDescription>
       offer(f1_.CreateOffer(opts, NULL));
@@ -511,8 +518,8 @@
   ASSERT_EQ(offered_video_codec.id, offered_data_codec.id);
 
   MediaSessionOptions opts;
-  opts.has_audio = true;
-  opts.has_video = true;
+  opts.recv_audio = true;
+  opts.recv_video = true;
   opts.data_channel_type = cricket::DCT_RTP;
   opts.bundle_enabled = true;
   rtc::scoped_ptr<SessionDescription>
@@ -541,8 +548,8 @@
   f1_.set_secure(SEC_ENABLED);
   f2_.set_secure(SEC_ENABLED);
   MediaSessionOptions opts;
-  opts.has_audio = true;
-  opts.has_video = false;
+  opts.recv_audio = true;
+  opts.recv_video = false;
   opts.data_channel_type = cricket::DCT_NONE;
   opts.bundle_enabled = true;
   rtc::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
@@ -550,8 +557,8 @@
       f2_.CreateAnswer(offer.get(), opts, NULL));
 
   MediaSessionOptions updated_opts;
-  updated_opts.has_audio = true;
-  updated_opts.has_video = true;
+  updated_opts.recv_audio = true;
+  updated_opts.recv_video = true;
   updated_opts.data_channel_type = cricket::DCT_RTP;
   updated_opts.bundle_enabled = true;
   rtc::scoped_ptr<SessionDescription> updated_offer(f1_.CreateOffer(
@@ -612,7 +619,7 @@
 // Create an SCTP data offer with bundle without error.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSctpDataOffer) {
   MediaSessionOptions opts;
-  opts.has_audio = false;
+  opts.recv_audio = false;
   opts.bundle_enabled = true;
   opts.data_channel_type = cricket::DCT_SCTP;
   f1_.set_secure(SEC_ENABLED);
@@ -625,7 +632,7 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestCreateOfferWithoutLegacyStreams) {
   MediaSessionOptions opts;
-  opts.has_video = true;
+  opts.recv_video = true;
   f1_.set_add_legacy_streams(false);
   rtc::scoped_ptr<SessionDescription>
       offer(f1_.CreateOffer(opts, NULL));
@@ -643,12 +650,30 @@
   EXPECT_FALSE(acd->has_ssrcs());             // No StreamParams.
 }
 
+// Creates an audio+video sendonly offer.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSendOnlyOffer) {
+  MediaSessionOptions options;
+  options.recv_audio = false;
+  options.recv_video = false;
+  options.AddSendStream(MEDIA_TYPE_VIDEO, kVideoTrack1, kMediaStream1);
+  options.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1);
+
+  rtc::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(options, NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  EXPECT_EQ(2u, offer->contents().size());
+  EXPECT_TRUE(IsMediaContentOfType(&offer->contents()[0], MEDIA_TYPE_AUDIO));
+  EXPECT_TRUE(IsMediaContentOfType(&offer->contents()[1], MEDIA_TYPE_VIDEO));
+
+  EXPECT_EQ(cricket::MD_SENDONLY, GetMediaDirection(&offer->contents()[0]));
+  EXPECT_EQ(cricket::MD_SENDONLY, GetMediaDirection(&offer->contents()[1]));
+}
+
 // Verifies that the order of the media contents in the current
 // SessionDescription is preserved in the new SessionDescription.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateOfferContentOrder) {
   MediaSessionOptions opts;
-  opts.has_audio = false;
-  opts.has_video = false;
+  opts.recv_audio = false;
+  opts.recv_video = false;
   opts.data_channel_type = cricket::DCT_SCTP;
 
   rtc::scoped_ptr<SessionDescription> offer1(f1_.CreateOffer(opts, NULL));
@@ -656,7 +681,7 @@
   EXPECT_EQ(1u, offer1->contents().size());
   EXPECT_TRUE(IsMediaContentOfType(&offer1->contents()[0], MEDIA_TYPE_DATA));
 
-  opts.has_video = true;
+  opts.recv_video = true;
   rtc::scoped_ptr<SessionDescription> offer2(
       f1_.CreateOffer(opts, offer1.get()));
   ASSERT_TRUE(offer2.get() != NULL);
@@ -664,7 +689,7 @@
   EXPECT_TRUE(IsMediaContentOfType(&offer2->contents()[0], MEDIA_TYPE_DATA));
   EXPECT_TRUE(IsMediaContentOfType(&offer2->contents()[1], MEDIA_TYPE_VIDEO));
 
-  opts.has_audio = true;
+  opts.recv_audio = true;
   rtc::scoped_ptr<SessionDescription> offer3(
       f1_.CreateOffer(opts, offer2.get()));
   ASSERT_TRUE(offer3.get() != NULL);
@@ -711,7 +736,7 @@
 // Create a typical video answer, and ensure it matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswer) {
   MediaSessionOptions opts;
-  opts.has_video = true;
+  opts.recv_video = true;
   f1_.set_secure(SEC_ENABLED);
   f2_.set_secure(SEC_ENABLED);
   rtc::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
@@ -781,19 +806,19 @@
   MediaSessionOptions opts;
 
   // Creates a data only offer.
-  opts.has_audio = false;
+  opts.recv_audio = false;
   opts.data_channel_type = cricket::DCT_SCTP;
   rtc::scoped_ptr<SessionDescription> offer1(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer1.get() != NULL);
 
   // Appends audio to the offer.
-  opts.has_audio = true;
+  opts.recv_audio = true;
   rtc::scoped_ptr<SessionDescription> offer2(
       f1_.CreateOffer(opts, offer1.get()));
   ASSERT_TRUE(offer2.get() != NULL);
 
   // Appends video to the offer.
-  opts.has_video = true;
+  opts.recv_video = true;
   rtc::scoped_ptr<SessionDescription> offer3(
       f1_.CreateOffer(opts, offer2.get()));
   ASSERT_TRUE(offer3.get() != NULL);
@@ -836,7 +861,7 @@
        CreateDataAnswerToOfferWithUnknownProtocol) {
   MediaSessionOptions opts;
   opts.data_channel_type = cricket::DCT_RTP;
-  opts.has_audio = false;
+  opts.recv_audio = false;
   f1_.set_secure(SEC_ENABLED);
   f2_.set_secure(SEC_ENABLED);
   rtc::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
@@ -891,7 +916,7 @@
 // matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestOfferAnswerWithRtpExtensions) {
   MediaSessionOptions opts;
-  opts.has_video = true;
+  opts.recv_video = true;
 
   f1_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension1));
   f1_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension1));
@@ -921,7 +946,7 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestCreateAnswerWithoutLegacyStreams) {
   MediaSessionOptions opts;
-  opts.has_video = true;
+  opts.recv_video = true;
   opts.data_channel_type = cricket::DCT_RTP;
   f1_.set_add_legacy_streams(false);
   f2_.set_add_legacy_streams(false);
@@ -948,7 +973,7 @@
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestPartial) {
   MediaSessionOptions opts;
-  opts.has_video = true;
+  opts.recv_video = true;
   opts.data_channel_type = cricket::DCT_RTP;
   f1_.set_secure(SEC_ENABLED);
   rtc::scoped_ptr<SessionDescription>
@@ -987,8 +1012,8 @@
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswerRtcpMux) {
   MediaSessionOptions offer_opts;
   MediaSessionOptions answer_opts;
-  answer_opts.has_video = true;
-  offer_opts.has_video = true;
+  answer_opts.recv_video = true;
+  offer_opts.recv_video = true;
   answer_opts.data_channel_type = cricket::DCT_RTP;
   offer_opts.data_channel_type = cricket::DCT_RTP;
 
@@ -1071,7 +1096,7 @@
 // Create an audio-only answer to a video offer.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswerToVideo) {
   MediaSessionOptions opts;
-  opts.has_video = true;
+  opts.recv_video = true;
   rtc::scoped_ptr<SessionDescription>
       offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
@@ -1106,7 +1131,7 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        CreateAnswerToOfferWithRejectedMedia) {
   MediaSessionOptions opts;
-  opts.has_video = true;
+  opts.recv_video = true;
   opts.data_channel_type = cricket::DCT_RTP;
   rtc::scoped_ptr<SessionDescription>
       offer(f1_.CreateOffer(opts, NULL));
@@ -1141,12 +1166,12 @@
 // adding a new video track and replaces one of the audio tracks.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoOffer) {
   MediaSessionOptions opts;
-  opts.AddStream(MEDIA_TYPE_VIDEO, kVideoTrack1, kMediaStream1);
-  opts.AddStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1);
-  opts.AddStream(MEDIA_TYPE_AUDIO, kAudioTrack2, kMediaStream1);
+  opts.AddSendStream(MEDIA_TYPE_VIDEO, kVideoTrack1, kMediaStream1);
+  opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1);
+  opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack2, kMediaStream1);
   opts.data_channel_type = cricket::DCT_RTP;
-  opts.AddStream(MEDIA_TYPE_DATA, kDataTrack1, kMediaStream1);
-  opts.AddStream(MEDIA_TYPE_DATA, kDataTrack2, kMediaStream1);
+  opts.AddSendStream(MEDIA_TYPE_DATA, kDataTrack1, kMediaStream1);
+  opts.AddSendStream(MEDIA_TYPE_DATA, kDataTrack2, kMediaStream1);
 
   f1_.set_secure(SEC_ENABLED);
   rtc::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
@@ -1214,11 +1239,11 @@
 
   // Update the offer. Add a new video track that is not synched to the
   // other tracks and replace audio track 2 with audio track 3.
-  opts.AddStream(MEDIA_TYPE_VIDEO, kVideoTrack2, kMediaStream2);
-  opts.RemoveStream(MEDIA_TYPE_AUDIO, kAudioTrack2);
-  opts.AddStream(MEDIA_TYPE_AUDIO, kAudioTrack3, kMediaStream1);
-  opts.RemoveStream(MEDIA_TYPE_DATA, kDataTrack2);
-  opts.AddStream(MEDIA_TYPE_DATA, kDataTrack3, kMediaStream1);
+  opts.AddSendStream(MEDIA_TYPE_VIDEO, kVideoTrack2, kMediaStream2);
+  opts.RemoveSendStream(MEDIA_TYPE_AUDIO, kAudioTrack2);
+  opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack3, kMediaStream1);
+  opts.RemoveSendStream(MEDIA_TYPE_DATA, kDataTrack2);
+  opts.AddSendStream(MEDIA_TYPE_DATA, kDataTrack3, kMediaStream1);
   rtc::scoped_ptr<SessionDescription>
       updated_offer(f1_.CreateOffer(opts, offer.get()));
 
@@ -1276,7 +1301,7 @@
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSimulcastVideoOffer) {
   MediaSessionOptions opts;
   const int num_sim_layers = 3;
-  opts.AddVideoStream(kVideoTrack1, kMediaStream1, num_sim_layers);
+  opts.AddSendVideoStream(kVideoTrack1, kMediaStream1, num_sim_layers);
   rtc::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
 
   ASSERT_TRUE(offer.get() != NULL);
@@ -1302,7 +1327,7 @@
 // adding a new video track and removes one of the audio tracks.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoAnswer) {
   MediaSessionOptions offer_opts;
-  offer_opts.has_video = true;
+  offer_opts.recv_video = true;
   offer_opts.data_channel_type = cricket::DCT_RTP;
   f1_.set_secure(SEC_ENABLED);
   f2_.set_secure(SEC_ENABLED);
@@ -1310,12 +1335,12 @@
                                                                   NULL));
 
   MediaSessionOptions opts;
-  opts.AddStream(MEDIA_TYPE_VIDEO, kVideoTrack1, kMediaStream1);
-  opts.AddStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1);
-  opts.AddStream(MEDIA_TYPE_AUDIO, kAudioTrack2, kMediaStream1);
+  opts.AddSendStream(MEDIA_TYPE_VIDEO, kVideoTrack1, kMediaStream1);
+  opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1);
+  opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack2, kMediaStream1);
   opts.data_channel_type = cricket::DCT_RTP;
-  opts.AddStream(MEDIA_TYPE_DATA, kDataTrack1, kMediaStream1);
-  opts.AddStream(MEDIA_TYPE_DATA, kDataTrack2, kMediaStream1);
+  opts.AddSendStream(MEDIA_TYPE_DATA, kDataTrack1, kMediaStream1);
+  opts.AddSendStream(MEDIA_TYPE_DATA, kDataTrack2, kMediaStream1);
 
   rtc::scoped_ptr<SessionDescription>
       answer(f2_.CreateAnswer(offer.get(), opts, NULL));
@@ -1382,9 +1407,9 @@
 
   // Update the answer. Add a new video track that is not synched to the
   // other traacks and remove 1 audio track.
-  opts.AddStream(MEDIA_TYPE_VIDEO, kVideoTrack2, kMediaStream2);
-  opts.RemoveStream(MEDIA_TYPE_AUDIO, kAudioTrack2);
-  opts.RemoveStream(MEDIA_TYPE_DATA, kDataTrack2);
+  opts.AddSendStream(MEDIA_TYPE_VIDEO, kVideoTrack2, kMediaStream2);
+  opts.RemoveSendStream(MEDIA_TYPE_AUDIO, kAudioTrack2);
+  opts.RemoveSendStream(MEDIA_TYPE_DATA, kDataTrack2);
   rtc::scoped_ptr<SessionDescription>
       updated_answer(f2_.CreateAnswer(offer.get(), opts, answer.get()));
 
@@ -1438,8 +1463,8 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        RespondentCreatesOfferAfterCreatingAnswer) {
   MediaSessionOptions opts;
-  opts.has_audio = true;
-  opts.has_video = true;
+  opts.recv_audio = true;
+  opts.recv_video = true;
 
   rtc::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   rtc::scoped_ptr<SessionDescription> answer(
@@ -1490,8 +1515,8 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        RespondentCreatesOfferAfterCreatingAnswerWithRtx) {
   MediaSessionOptions opts;
-  opts.has_video = true;
-  opts.has_audio = false;
+  opts.recv_video = true;
+  opts.recv_audio = false;
   std::vector<VideoCodec> f1_codecs = MAKE_VECTOR(kVideoCodecs1);
   VideoCodec rtx_f1;
   rtx_f1.id = 126;
@@ -1562,8 +1587,8 @@
   f1_.set_video_codecs(f1_codecs);
 
   MediaSessionOptions opts;
-  opts.has_audio = true;
-  opts.has_video = false;
+  opts.recv_audio = true;
+  opts.recv_video = false;
 
   rtc::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   rtc::scoped_ptr<SessionDescription> answer(
@@ -1576,8 +1601,8 @@
   // Now - let |f2_| add video with RTX and let the payload type the RTX codec
   // reference  be the same as an audio codec that was negotiated in the
   // first offer/answer exchange.
-  opts.has_audio = true;
-  opts.has_video = true;
+  opts.recv_audio = true;
+  opts.recv_video = true;
 
   std::vector<VideoCodec> f2_codecs = MAKE_VECTOR(kVideoCodecs2);
   int used_pl_type = acd->codecs()[0].id;
@@ -1616,8 +1641,8 @@
 // Test that RTX is ignored when there is no associated payload type parameter.
 TEST_F(MediaSessionDescriptionFactoryTest, RtxWithoutApt) {
   MediaSessionOptions opts;
-  opts.has_video = true;
-  opts.has_audio = false;
+  opts.recv_video = true;
+  opts.recv_audio = false;
   std::vector<VideoCodec> f1_codecs = MAKE_VECTOR(kVideoCodecs1);
   VideoCodec rtx_f1;
   rtx_f1.id = 126;
@@ -1674,8 +1699,8 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        RespondentCreatesOfferAfterCreatingAnswerWithRtpExtensions) {
   MediaSessionOptions opts;
-  opts.has_audio = true;
-  opts.has_video = true;
+  opts.recv_audio = true;
+  opts.recv_video = true;
 
   f1_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension1));
   f1_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension1));
@@ -1763,20 +1788,20 @@
 // ensure the TransportInfo in the SessionDescription matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferAudio) {
   MediaSessionOptions options;
-  options.has_audio = true;
+  options.recv_audio = true;
   TestTransportInfo(true, options, false);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferAudioCurrent) {
   MediaSessionOptions options;
-  options.has_audio = true;
+  options.recv_audio = true;
   TestTransportInfo(true, options, true);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferMultimedia) {
   MediaSessionOptions options;
-  options.has_audio = true;
-  options.has_video = true;
+  options.recv_audio = true;
+  options.recv_video = true;
   options.data_channel_type = cricket::DCT_RTP;
   TestTransportInfo(true, options, false);
 }
@@ -1784,16 +1809,16 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
     TestTransportInfoOfferMultimediaCurrent) {
   MediaSessionOptions options;
-  options.has_audio = true;
-  options.has_video = true;
+  options.recv_audio = true;
+  options.recv_video = true;
   options.data_channel_type = cricket::DCT_RTP;
   TestTransportInfo(true, options, true);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferBundle) {
   MediaSessionOptions options;
-  options.has_audio = true;
-  options.has_video = true;
+  options.recv_audio = true;
+  options.recv_video = true;
   options.data_channel_type = cricket::DCT_RTP;
   options.bundle_enabled = true;
   TestTransportInfo(true, options, false);
@@ -1802,8 +1827,8 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestTransportInfoOfferBundleCurrent) {
   MediaSessionOptions options;
-  options.has_audio = true;
-  options.has_video = true;
+  options.recv_audio = true;
+  options.recv_video = true;
   options.data_channel_type = cricket::DCT_RTP;
   options.bundle_enabled = true;
   TestTransportInfo(true, options, true);
@@ -1811,21 +1836,21 @@
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerAudio) {
   MediaSessionOptions options;
-  options.has_audio = true;
+  options.recv_audio = true;
   TestTransportInfo(false, options, false);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest,
     TestTransportInfoAnswerAudioCurrent) {
   MediaSessionOptions options;
-  options.has_audio = true;
+  options.recv_audio = true;
   TestTransportInfo(false, options, true);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerMultimedia) {
   MediaSessionOptions options;
-  options.has_audio = true;
-  options.has_video = true;
+  options.recv_audio = true;
+  options.recv_video = true;
   options.data_channel_type = cricket::DCT_RTP;
   TestTransportInfo(false, options, false);
 }
@@ -1833,16 +1858,16 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
     TestTransportInfoAnswerMultimediaCurrent) {
   MediaSessionOptions options;
-  options.has_audio = true;
-  options.has_video = true;
+  options.recv_audio = true;
+  options.recv_video = true;
   options.data_channel_type = cricket::DCT_RTP;
   TestTransportInfo(false, options, true);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerBundle) {
   MediaSessionOptions options;
-  options.has_audio = true;
-  options.has_video = true;
+  options.recv_audio = true;
+  options.recv_video = true;
   options.data_channel_type = cricket::DCT_RTP;
   options.bundle_enabled = true;
   TestTransportInfo(false, options, false);
@@ -1851,8 +1876,8 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
     TestTransportInfoAnswerBundleCurrent) {
   MediaSessionOptions options;
-  options.has_audio = true;
-  options.has_video = true;
+  options.recv_audio = true;
+  options.recv_video = true;
   options.data_channel_type = cricket::DCT_RTP;
   options.bundle_enabled = true;
   TestTransportInfo(false, options, true);
@@ -1936,8 +1961,8 @@
   tdf1_.set_secure(SEC_ENABLED);
   tdf2_.set_secure(SEC_DISABLED);
   MediaSessionOptions options;
-  options.has_audio = true;
-  options.has_video = true;
+  options.recv_audio = true;
+  options.recv_video = true;
   rtc::scoped_ptr<SessionDescription> offer, answer;
   const cricket::MediaContentDescription* audio_media_desc;
   const cricket::MediaContentDescription* video_media_desc;
@@ -2055,8 +2080,8 @@
   tdf1_.set_secure(SEC_ENABLED);
   tdf2_.set_secure(SEC_ENABLED);
   MediaSessionOptions options;
-  options.has_audio = true;
-  options.has_video = true;
+  options.recv_audio = true;
+  options.recv_video = true;
   options.data_channel_type = cricket::DCT_RTP;
 
   rtc::scoped_ptr<SessionDescription> offer, answer;
@@ -2104,8 +2129,8 @@
 // offer or answer.
 TEST_F(MediaSessionDescriptionFactoryTest, TestVADEnableOption) {
   MediaSessionOptions options;
-  options.has_audio = true;
-  options.has_video = true;
+  options.recv_audio = true;
+  options.recv_video = true;
   rtc::scoped_ptr<SessionDescription> offer(
       f1_.CreateOffer(options, NULL));
   ASSERT_TRUE(offer.get() != NULL);
diff --git a/talk/session/media/mediasessionclient_unittest.cc b/talk/session/media/mediasessionclient_unittest.cc
index d313f81..bb3043d 100644
--- a/talk/session/media/mediasessionclient_unittest.cc
+++ b/talk/session/media/mediasessionclient_unittest.cc
@@ -1088,8 +1088,8 @@
 // but not video or data.
 static cricket::CallOptions AudioCallOptions() {
   cricket::CallOptions options;
-  options.has_audio = true;
-  options.has_video = false;
+  options.recv_audio = true;
+  options.recv_video = false;
   options.data_channel_type = cricket::DCT_NONE;
   return options;
 }
@@ -1098,8 +1098,8 @@
 // enabled, but not data.
 static cricket::CallOptions VideoCallOptions() {
   cricket::CallOptions options;
-  options.has_audio = true;
-  options.has_video = true;
+  options.recv_audio = true;
+  options.recv_video = true;
   options.data_channel_type = cricket::DCT_NONE;
   return options;
 }
@@ -1811,13 +1811,13 @@
     // The NextContent method actually returns the second content. So we
     // can't handle the case when audio, video and data are all enabled. But
     // since we are testing rejection, it won't be the case.
-    if (options.has_audio) {
+    if (options.has_audio()) {
       ASSERT_TRUE(content != NULL);
       ASSERT_EQ("test audio", content->Attr(buzz::QName("", "name")));
       content = parser_->NextContent(content);
     }
 
-    if (options.has_video) {
+    if (options.has_video()) {
       ASSERT_TRUE(content != NULL);
       ASSERT_EQ("test video", content->Attr(buzz::QName("", "name")));
       content = parser_->NextContent(content);
@@ -2022,7 +2022,7 @@
       ASSERT_TRUE(encryption == NULL);
     }
 
-    if (options.has_video) {
+    if (options.has_video()) {
       CheckVideoBandwidth(options.video_bandwidth,
                           call_->sessions()[0]->local_description());
       CheckVideoRtcpMux(expected_video_rtcp_mux_,
@@ -2826,7 +2826,7 @@
   rtc::scoped_ptr<MediaSessionClientTest> test(JingleTest());
   rtc::scoped_ptr<buzz::XmlElement> elem;
   cricket::CallOptions options = VideoCallOptions();
-  options.has_audio = false;
+  options.recv_audio = false;
   options.data_channel_type = cricket::DCT_RTP;
   test->TestRejectOffer(kJingleVideoInitiateWithRtpData, options, elem.use());
 }