Changeset 239136 in webkit


Ignore:
Timestamp:
Dec 12, 2018 5:13:29 PM (5 years ago)
Author:
youenn@apple.com
Message:

Recycling the m section should work if it was rejected remotely
https://bugs.webkit.org/show_bug.cgi?id=192636

Reviewed by Eric Carlson.

Source/ThirdParty/libwebrtc:

Changes merged from https://webrtc.googlesource.com/src.git/+/5c72e71e14cfa76a2d1b0979d6b918abe187c208

  • Source/webrtc/pc/mediasession.cc:
  • Source/webrtc/pc/mediasession.h:
  • Source/webrtc/pc/mediasession_unittest.cc:
  • Source/webrtc/pc/peerconnection.cc:
  • Source/webrtc/pc/peerconnection_jsep_unittest.cc:

LayoutTests:

  • webrtc/msection-recycling-expected.txt: Added.
  • webrtc/msection-recycling.html: Added.
Location:
trunk
Files:
2 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r239135 r239136  
     12018-12-12  Youenn Fablet  <youenn@apple.com>
     2
     3        Recycling the m section should work if it was rejected remotely
     4        https://bugs.webkit.org/show_bug.cgi?id=192636
     5
     6        Reviewed by Eric Carlson.
     7
     8        * webrtc/msection-recycling-expected.txt: Added.
     9        * webrtc/msection-recycling.html: Added.
     10
    1112018-12-12  Tim Horton  <timothy_horton@apple.com>
    212
  • trunk/Source/ThirdParty/libwebrtc/ChangeLog

    r238996 r239136  
     12018-12-12  Youenn Fablet  <youenn@apple.com>
     2
     3        Recycling the m section should work if it was rejected remotely
     4        https://bugs.webkit.org/show_bug.cgi?id=192636
     5
     6        Reviewed by Eric Carlson.
     7
     8        Changes merged from https://webrtc.googlesource.com/src.git/+/5c72e71e14cfa76a2d1b0979d6b918abe187c208
     9
     10        * Source/webrtc/pc/mediasession.cc:
     11        * Source/webrtc/pc/mediasession.h:
     12        * Source/webrtc/pc/mediasession_unittest.cc:
     13        * Source/webrtc/pc/peerconnection.cc:
     14        * Source/webrtc/pc/peerconnection_jsep_unittest.cc:
     15
    1162018-12-07  Youenn Fablet  <youenn@apple.com>
    217
  • trunk/Source/ThirdParty/libwebrtc/Source/webrtc/pc/mediasession.cc

    r238967 r239136  
    1919#include <utility>
    2020
     21#include "absl/memory/memory.h"
    2122#include "absl/strings/match.h"
    2223#include "absl/types/optional.h"
     
    290291
    291292// Finds all StreamParams of all media types and attach them to stream_params.
    292 static void GetCurrentStreamParams(const SessionDescription* sdesc,
    293                                    StreamParamsVec* stream_params) {
    294   RTC_DCHECK(stream_params);
    295   if (!sdesc) {
    296     return;
    297   }
    298   for (const ContentInfo& content : sdesc->contents()) {
    299     if (!content.media_description()) {
    300       continue;
    301     }
    302     for (const StreamParams& params : content.media_description()->streams()) {
    303       stream_params->push_back(params);
    304     }
    305   }
     293static StreamParamsVec GetCurrentStreamParams(
     294    const std::vector<const ContentInfo*>& active_local_contents) {
     295  StreamParamsVec stream_params;
     296  for (const ContentInfo* content : active_local_contents) {
     297    for (const StreamParams& params : content->media_description()->streams()) {
     298      stream_params.push_back(params);
     299    }
     300  }
     301  return stream_params;
    306302}
    307303
     
    643639}
    644640
     641static std::vector<const ContentInfo*> GetActiveContents(
     642    const SessionDescription& description,
     643    const MediaSessionOptions& session_options) {
     644  std::vector<const ContentInfo*> active_contents;
     645  for (size_t i = 0; i < description.contents().size(); ++i) {
     646    RTC_DCHECK_LT(i, session_options.media_description_options.size());
     647    const ContentInfo& content = description.contents()[i];
     648    const MediaDescriptionOptions& media_options =
     649        session_options.media_description_options[i];
     650    if (!content.rejected && !media_options.stopped &&
     651        content.name == media_options.mid) {
     652      active_contents.push_back(&content);
     653    }
     654  }
     655  return active_contents;
     656}
     657
    645658template <class C>
    646659static bool ContainsRtxCodec(const std::vector<C>& codecs) {
     
    12661279    const MediaSessionOptions& session_options,
    12671280    const SessionDescription* current_description) const {
    1268   std::unique_ptr<SessionDescription> offer(new SessionDescription());
     1281  // Must have options for each existing section.
     1282  if (current_description) {
     1283    RTC_DCHECK_LE(current_description->contents().size(),
     1284                  session_options.media_description_options.size());
     1285  }
    12691286
    12701287  IceCredentialsIterator ice_credentials(
    12711288      session_options.pooled_ice_credentials);
    1272   StreamParamsVec current_streams;
    1273   GetCurrentStreamParams(current_description, &current_streams);
     1289
     1290  std::vector<const ContentInfo*> current_active_contents;
     1291  if (current_description) {
     1292    current_active_contents =
     1293        GetActiveContents(*current_description, session_options);
     1294  }
     1295
     1296  StreamParamsVec current_streams =
     1297      GetCurrentStreamParams(current_active_contents);
    12741298
    12751299  AudioCodecs offer_audio_codecs;
    12761300  VideoCodecs offer_video_codecs;
    12771301  DataCodecs offer_data_codecs;
    1278   GetCodecsForOffer(current_description, &offer_audio_codecs,
     1302  GetCodecsForOffer(current_active_contents, &offer_audio_codecs,
    12791303                    &offer_video_codecs, &offer_data_codecs);
    12801304
     
    12881312  RtpHeaderExtensions audio_rtp_extensions;
    12891313  RtpHeaderExtensions video_rtp_extensions;
    1290   GetRtpHdrExtsToOffer(session_options, current_description,
     1314  GetRtpHdrExtsToOffer(current_active_contents, session_options.is_unified_plan,
    12911315                       &audio_rtp_extensions, &video_rtp_extensions);
    12921316
    1293   // Must have options for each existing section.
    1294   if (current_description) {
    1295     RTC_DCHECK(current_description->contents().size() <=
    1296                session_options.media_description_options.size());
    1297   }
     1317  auto offer = absl::make_unique<SessionDescription>();
    12981318
    12991319  // Iterate through the media description options, matching with existing media
     
    13071327      current_content = &current_description->contents()[msection_index];
    13081328      // Media type must match unless this media section is being recycled.
    1309       RTC_DCHECK(current_content->rejected ||
     1329      RTC_DCHECK(current_content->name != media_description_options.mid ||
    13101330                 IsMediaContentOfType(current_content,
    13111331                                      media_description_options.type));
     
    13921412  }
    13931413
     1414  // Must have options for exactly as many sections as in the offer.
     1415  RTC_DCHECK_EQ(offer->contents().size(),
     1416                session_options.media_description_options.size());
     1417
    13941418  IceCredentialsIterator ice_credentials(
    13951419      session_options.pooled_ice_credentials);
    13961420
    1397   // The answer contains the intersection of the codecs in the offer with the
    1398   // codecs we support. As indicated by XEP-0167, we retain the same payload ids
    1399   // from the offer in the answer.
    1400   std::unique_ptr<SessionDescription> answer(new SessionDescription());
    1401 
    1402   StreamParamsVec current_streams;
    1403   GetCurrentStreamParams(current_description, &current_streams);
    1404 
    1405   // If the offer supports BUNDLE, and we want to use it too, create a BUNDLE
    1406   // group in the answer with the appropriate content names.
    1407   const ContentGroup* offer_bundle = offer->GetGroupByName(GROUP_TYPE_BUNDLE);
    1408   ContentGroup answer_bundle(GROUP_TYPE_BUNDLE);
    1409   // Transport info shared by the bundle group.
    1410   std::unique_ptr<TransportInfo> bundle_transport;
    1411 
    1412   answer->set_extmap_allow_mixed(offer->extmap_allow_mixed());
     1421  std::vector<const ContentInfo*> current_active_contents;
     1422  if (current_description) {
     1423    current_active_contents =
     1424        GetActiveContents(*current_description, session_options);
     1425  }
     1426
     1427  StreamParamsVec current_streams =
     1428      GetCurrentStreamParams(current_active_contents);
    14131429
    14141430  // Get list of all possible codecs that respects existing payload type
     
    14211437  VideoCodecs answer_video_codecs;
    14221438  DataCodecs answer_data_codecs;
    1423   GetCodecsForAnswer(current_description, offer, &answer_audio_codecs,
     1439  GetCodecsForAnswer(current_active_contents, *offer, &answer_audio_codecs,
    14241440                     &answer_video_codecs, &answer_data_codecs);
    14251441
     
    14311447                   session_options.data_channel_type == DCT_SCTP);
    14321448
    1433   // Must have options for exactly as many sections as in the offer.
    1434   RTC_DCHECK(offer->contents().size() ==
    1435              session_options.media_description_options.size());
     1449  auto answer = absl::make_unique<SessionDescription>();
     1450
     1451  // If the offer supports BUNDLE, and we want to use it too, create a BUNDLE
     1452  // group in the answer with the appropriate content names.
     1453  const ContentGroup* offer_bundle = offer->GetGroupByName(GROUP_TYPE_BUNDLE);
     1454  ContentGroup answer_bundle(GROUP_TYPE_BUNDLE);
     1455  // Transport info shared by the bundle group.
     1456  std::unique_ptr<TransportInfo> bundle_transport;
     1457
     1458  answer->set_extmap_allow_mixed(offer->extmap_allow_mixed());
     1459
    14361460  // Iterate through the media description options, matching with existing
    14371461  // media descriptions in |current_description|.
     
    15901614}
    15911615
    1592 void MergeCodecsFromDescription(const SessionDescription* description,
    1593                                 AudioCodecs* audio_codecs,
    1594                                 VideoCodecs* video_codecs,
    1595                                 DataCodecs* data_codecs,
    1596                                 UsedPayloadTypes* used_pltypes) {
    1597   RTC_DCHECK(description);
    1598   for (const ContentInfo& content : description->contents()) {
    1599     if (IsMediaContentOfType(&content, MEDIA_TYPE_AUDIO)) {
     1616void MergeCodecsFromDescription(
     1617    const std::vector<const ContentInfo*>& current_active_contents,
     1618    AudioCodecs* audio_codecs,
     1619    VideoCodecs* video_codecs,
     1620    DataCodecs* data_codecs,
     1621    UsedPayloadTypes* used_pltypes) {
     1622  for (const ContentInfo* content : current_active_contents) {
     1623    if (IsMediaContentOfType(content, MEDIA_TYPE_AUDIO)) {
    16001624      const AudioContentDescription* audio =
    1601           content.media_description()->as_audio();
     1625          content->media_description()->as_audio();
    16021626      MergeCodecs<AudioCodec>(audio->codecs(), audio_codecs, used_pltypes);
    1603     } else if (IsMediaContentOfType(&content, MEDIA_TYPE_VIDEO)) {
     1627    } else if (IsMediaContentOfType(content, MEDIA_TYPE_VIDEO)) {
    16041628      const VideoContentDescription* video =
    1605           content.media_description()->as_video();
     1629          content->media_description()->as_video();
    16061630      MergeCodecs<VideoCodec>(video->codecs(), video_codecs, used_pltypes);
    1607     } else if (IsMediaContentOfType(&content, MEDIA_TYPE_DATA)) {
     1631    } else if (IsMediaContentOfType(content, MEDIA_TYPE_DATA)) {
    16081632      const DataContentDescription* data =
    1609           content.media_description()->as_data();
     1633          content->media_description()->as_data();
    16101634      MergeCodecs<DataCodec>(data->codecs(), data_codecs, used_pltypes);
    16111635    }
     
    16201644//    on the directional attribute (happens in another method).
    16211645void MediaSessionDescriptionFactory::GetCodecsForOffer(
    1622     const SessionDescription* current_description,
     1646    const std::vector<const ContentInfo*>& current_active_contents,
    16231647    AudioCodecs* audio_codecs,
    16241648    VideoCodecs* video_codecs,
    16251649    DataCodecs* data_codecs) const {
    1626   UsedPayloadTypes used_pltypes;
    1627   audio_codecs->clear();
    1628   video_codecs->clear();
    1629   data_codecs->clear();
    1630 
    16311650  // First - get all codecs from the current description if the media type
    16321651  // is used. Add them to |used_pltypes| so the payload type is not reused if a
    16331652  // new media type is added.
    1634   if (current_description) {
    1635     MergeCodecsFromDescription(current_description, audio_codecs, video_codecs,
    1636                                data_codecs, &used_pltypes);
    1637   }
    1638 
    1639   // Add our codecs that are not in |current_description|.
     1653  UsedPayloadTypes used_pltypes;
     1654  MergeCodecsFromDescription(current_active_contents, audio_codecs,
     1655                             video_codecs, data_codecs, &used_pltypes);
     1656
     1657  // Add our codecs that are not in the current description.
    16401658  MergeCodecs<AudioCodec>(all_audio_codecs_, audio_codecs, &used_pltypes);
    16411659  MergeCodecs<VideoCodec>(video_codecs_, video_codecs, &used_pltypes);
     
    16511669//    on the directional attribute (happens in another method).
    16521670void MediaSessionDescriptionFactory::GetCodecsForAnswer(
    1653     const SessionDescription* current_description,
    1654     const SessionDescription* remote_offer,
     1671    const std::vector<const ContentInfo*>& current_active_contents,
     1672    const SessionDescription& remote_offer,
    16551673    AudioCodecs* audio_codecs,
    16561674    VideoCodecs* video_codecs,
    16571675    DataCodecs* data_codecs) const {
    1658   UsedPayloadTypes used_pltypes;
    1659   audio_codecs->clear();
    1660   video_codecs->clear();
    1661   data_codecs->clear();
    1662 
    16631676  // First - get all codecs from the current description if the media type
    16641677  // is used. Add them to |used_pltypes| so the payload type is not reused if a
    16651678  // new media type is added.
    1666   if (current_description) {
    1667     MergeCodecsFromDescription(current_description, audio_codecs, video_codecs,
    1668                                data_codecs, &used_pltypes);
    1669   }
     1679  UsedPayloadTypes used_pltypes;
     1680  MergeCodecsFromDescription(current_active_contents, audio_codecs,
     1681                             video_codecs, data_codecs, &used_pltypes);
    16701682
    16711683  // Second - filter out codecs that we don't support at all and should ignore.
     
    16731685  VideoCodecs filtered_offered_video_codecs;
    16741686  DataCodecs filtered_offered_data_codecs;
    1675   for (const ContentInfo& content : remote_offer->contents()) {
     1687  for (const ContentInfo& content : remote_offer.contents()) {
    16761688    if (IsMediaContentOfType(&content, MEDIA_TYPE_AUDIO)) {
    16771689      const AudioContentDescription* audio =
     
    17131725  }
    17141726
    1715   // Add codecs that are not in |current_description| but were in
     1727  // Add codecs that are not in the current description but were in
    17161728  // |remote_offer|.
    17171729  MergeCodecs<AudioCodec>(filtered_offered_audio_codecs, audio_codecs,
     
    17231735}
    17241736
     1737// TODO(steveanton): Replace |is_unified_plan| flag with a member variable.
    17251738void MediaSessionDescriptionFactory::GetRtpHdrExtsToOffer(
    1726     const MediaSessionOptions& session_options,
    1727     const SessionDescription* current_description,
     1739    const std::vector<const ContentInfo*>& current_active_contents,
     1740    bool is_unified_plan,
    17281741    RtpHeaderExtensions* offer_audio_extensions,
    17291742    RtpHeaderExtensions* offer_video_extensions) const {
     
    17331746  RtpHeaderExtensions all_regular_extensions;
    17341747  RtpHeaderExtensions all_encrypted_extensions;
    1735   offer_audio_extensions->clear();
    1736   offer_video_extensions->clear();
    17371748
    17381749  // First - get all extensions from the current description if the media type
     
    17401751  // Add them to |used_ids| so the local ids are not reused if a new media
    17411752  // type is added.
    1742   if (current_description) {
    1743     for (const ContentInfo& content : current_description->contents()) {
    1744       if (IsMediaContentOfType(&content, MEDIA_TYPE_AUDIO)) {
    1745         const AudioContentDescription* audio =
    1746             content.media_description()->as_audio();
    1747         MergeRtpHdrExts(audio->rtp_header_extensions(), offer_audio_extensions,
    1748                         &all_regular_extensions, &all_encrypted_extensions,
    1749                         &used_ids);
    1750       } else if (IsMediaContentOfType(&content, MEDIA_TYPE_VIDEO)) {
    1751         const VideoContentDescription* video =
    1752             content.media_description()->as_video();
    1753         MergeRtpHdrExts(video->rtp_header_extensions(), offer_video_extensions,
    1754                         &all_regular_extensions, &all_encrypted_extensions,
    1755                         &used_ids);
    1756       }
    1757     }
    1758   }
    1759 
    1760   // Add our default RTP header extensions that are not in
    1761   // |current_description|.
    1762   MergeRtpHdrExts(audio_rtp_header_extensions(session_options.is_unified_plan),
     1753  for (const ContentInfo* content : current_active_contents) {
     1754    if (IsMediaContentOfType(content, MEDIA_TYPE_AUDIO)) {
     1755      const AudioContentDescription* audio =
     1756          content->media_description()->as_audio();
     1757      MergeRtpHdrExts(audio->rtp_header_extensions(), offer_audio_extensions,
     1758                      &all_regular_extensions, &all_encrypted_extensions,
     1759                      &used_ids);
     1760    } else if (IsMediaContentOfType(content, MEDIA_TYPE_VIDEO)) {
     1761      const VideoContentDescription* video =
     1762          content->media_description()->as_video();
     1763      MergeRtpHdrExts(video->rtp_header_extensions(), offer_video_extensions,
     1764                      &all_regular_extensions, &all_encrypted_extensions,
     1765                      &used_ids);
     1766    }
     1767  }
     1768
     1769  // Add our default RTP header extensions that are not in the current
     1770  // description.
     1771  MergeRtpHdrExts(audio_rtp_header_extensions(is_unified_plan),
    17631772                  offer_audio_extensions, &all_regular_extensions,
    17641773                  &all_encrypted_extensions, &used_ids);
    1765   MergeRtpHdrExts(video_rtp_header_extensions(session_options.is_unified_plan),
     1774  MergeRtpHdrExts(video_rtp_header_extensions(is_unified_plan),
    17661775                  offer_video_extensions, &all_regular_extensions,
    17671776                  &all_encrypted_extensions, &used_ids);
     
    17691778  // TODO(jbauch): Support adding encrypted header extensions to existing
    17701779  // sessions.
    1771   if (enable_encrypted_rtp_header_extensions_ && !current_description) {
     1780  if (enable_encrypted_rtp_header_extensions_ &&
     1781      current_active_contents.empty()) {
    17721782    AddEncryptedVersionsOfHdrExts(offer_audio_extensions,
    17731783                                  &all_encrypted_extensions, &used_ids);
     
    18591869
    18601870  AudioCodecs filtered_codecs;
    1861   // Add the codecs from current content if it exists and is not being recycled.
    1862   if (current_content && !current_content->rejected) {
     1871  // Add the codecs from current content if it exists and is not rejected nor
     1872  // recycled.
     1873  if (current_content && !current_content->rejected &&
     1874      current_content->name == media_description_options.mid) {
    18631875    RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO));
    18641876    const AudioContentDescription* acd =
     
    19351947
    19361948  VideoCodecs filtered_codecs;
    1937   // Add the codecs from current content if it exists and is not being recycled.
    1938   if (current_content && !current_content->rejected) {
     1949  // Add the codecs from current content if it exists and is not rejected nor
     1950  // recycled.
     1951  if (current_content && !current_content->rejected &&
     1952      current_content->name == media_description_options.mid) {
    19391953    RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO));
    19401954    const VideoContentDescription* vcd =
     
    20982112
    20992113  AudioCodecs filtered_codecs;
    2100   // Add the codecs from current content if it exists and is not being recycled.
    2101   if (current_content && !current_content->rejected) {
     2114  // Add the codecs from current content if it exists and is not rejected nor
     2115  // recycled.
     2116  if (current_content && !current_content->rejected &&
     2117      current_content->name == media_description_options.mid) {
    21022118    RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO));
    21032119    const AudioContentDescription* acd =
     
    21842200
    21852201  VideoCodecs filtered_codecs;
    2186   // Add the codecs from current content if it exists and is not being recycled.
    2187   if (current_content && !current_content->rejected) {
     2202  // Add the codecs from current content if it exists and is not rejected nor
     2203  // recycled.
     2204  if (current_content && !current_content->rejected &&
     2205      current_content->name == media_description_options.mid) {
    21882206    RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO));
    21892207    const VideoContentDescription* vcd =
  • trunk/Source/ThirdParty/libwebrtc/Source/webrtc/pc/mediasession.h

    r238967 r239136  
    174174      const webrtc::RtpTransceiverDirection& offer,
    175175      const webrtc::RtpTransceiverDirection& answer) const;
    176   void GetCodecsForOffer(const SessionDescription* current_description,
    177                          AudioCodecs* audio_codecs,
    178                          VideoCodecs* video_codecs,
    179                          DataCodecs* data_codecs) const;
    180   void GetCodecsForAnswer(const SessionDescription* current_description,
    181                           const SessionDescription* remote_offer,
    182                           AudioCodecs* audio_codecs,
    183                           VideoCodecs* video_codecs,
    184                           DataCodecs* data_codecs) const;
    185   void GetRtpHdrExtsToOffer(const MediaSessionOptions& session_options,
    186                             const SessionDescription* current_description,
    187                             RtpHeaderExtensions* audio_extensions,
    188                             RtpHeaderExtensions* video_extensions) const;
     176  void GetCodecsForOffer(
     177      const std::vector<const ContentInfo*>& current_active_contents,
     178      AudioCodecs* audio_codecs,
     179      VideoCodecs* video_codecs,
     180      DataCodecs* data_codecs) const;
     181  void GetCodecsForAnswer(
     182      const std::vector<const ContentInfo*>& current_active_contents,
     183      const SessionDescription& remote_offer,
     184      AudioCodecs* audio_codecs,
     185      VideoCodecs* video_codecs,
     186      DataCodecs* data_codecs) const;
     187  void GetRtpHdrExtsToOffer(
     188      const std::vector<const ContentInfo*>& current_active_contents,
     189      bool is_unified_plan,
     190      RtpHeaderExtensions* audio_extensions,
     191      RtpHeaderExtensions* video_extensions) const;
    189192  bool AddTransportOffer(const std::string& content_name,
    190193                         const TransportOptions& transport_options,
  • trunk/Source/ThirdParty/libwebrtc/Source/webrtc/pc/mediasession_unittest.cc

    r238967 r239136  
    20212021}
    20222022
     2023// Test that a reoffer does not reuse audio codecs from a previous media section
     2024// that is being recycled.
     2025TEST_F(MediaSessionDescriptionFactoryTest,
     2026       ReOfferDoesNotReUseRecycledAudioCodecs) {
     2027  f1_.set_video_codecs({});
     2028  f2_.set_video_codecs({});
     2029
     2030  MediaSessionOptions opts;
     2031  AddMediaSection(MEDIA_TYPE_AUDIO, "a0", RtpTransceiverDirection::kSendRecv,
     2032                  kActive, &opts);
     2033  auto offer = absl::WrapUnique(f1_.CreateOffer(opts, nullptr));
     2034  auto answer = absl::WrapUnique(f2_.CreateAnswer(offer.get(), opts, nullptr));
     2035
     2036  // Recycle the media section by changing its mid.
     2037  opts.media_description_options[0].mid = "a1";
     2038  auto reoffer = absl::WrapUnique(f2_.CreateOffer(opts, answer.get()));
     2039
     2040  // Expect that the results of the first negotiation are ignored. If the m=
     2041  // section was not recycled the payload types would match the initial offerer.
     2042  const AudioContentDescription* acd =
     2043      GetFirstAudioContentDescription(reoffer.get());
     2044  EXPECT_THAT(acd->codecs(), ElementsAreArray(kAudioCodecs2));
     2045}
     2046
     2047// Test that a reoffer does not reuse video codecs from a previous media section
     2048// that is being recycled.
     2049TEST_F(MediaSessionDescriptionFactoryTest,
     2050       ReOfferDoesNotReUseRecycledVideoCodecs) {
     2051  f1_.set_audio_codecs({}, {});
     2052  f2_.set_audio_codecs({}, {});
     2053
     2054  MediaSessionOptions opts;
     2055  AddMediaSection(MEDIA_TYPE_VIDEO, "v0", RtpTransceiverDirection::kSendRecv,
     2056                  kActive, &opts);
     2057  auto offer = absl::WrapUnique(f1_.CreateOffer(opts, nullptr));
     2058  auto answer = absl::WrapUnique(f2_.CreateAnswer(offer.get(), opts, nullptr));
     2059
     2060  // Recycle the media section by changing its mid.
     2061  opts.media_description_options[0].mid = "v1";
     2062  auto reoffer = absl::WrapUnique(f2_.CreateOffer(opts, answer.get()));
     2063
     2064  // Expect that the results of the first negotiation are ignored. If the m=
     2065  // section was not recycled the payload types would match the initial offerer.
     2066  const VideoContentDescription* vcd =
     2067      GetFirstVideoContentDescription(reoffer.get());
     2068  EXPECT_THAT(vcd->codecs(), ElementsAreArray(kVideoCodecs2));
     2069}
     2070
     2071// Test that a reanswer does not reuse audio codecs from a previous media
     2072// section that is being recycled.
     2073TEST_F(MediaSessionDescriptionFactoryTest,
     2074       ReAnswerDoesNotReUseRecycledAudioCodecs) {
     2075  f1_.set_video_codecs({});
     2076  f2_.set_video_codecs({});
     2077
     2078  // Perform initial offer/answer in reverse (|f2_| as offerer) so that the
     2079  // second offer/answer is forward (|f1_| as offerer).
     2080  MediaSessionOptions opts;
     2081  AddMediaSection(MEDIA_TYPE_AUDIO, "a0", RtpTransceiverDirection::kSendRecv,
     2082                  kActive, &opts);
     2083  auto offer = absl::WrapUnique(f2_.CreateOffer(opts, nullptr));
     2084  auto answer = absl::WrapUnique(f1_.CreateAnswer(offer.get(), opts, nullptr));
     2085
     2086  // Recycle the media section by changing its mid.
     2087  opts.media_description_options[0].mid = "a1";
     2088  auto reoffer = absl::WrapUnique(f1_.CreateOffer(opts, answer.get()));
     2089  auto reanswer =
     2090      absl::WrapUnique(f2_.CreateAnswer(reoffer.get(), opts, offer.get()));
     2091
     2092  // Expect that the results of the first negotiation are ignored. If the m=
     2093  // section was not recycled the payload types would match the initial offerer.
     2094  const AudioContentDescription* acd =
     2095      GetFirstAudioContentDescription(reanswer.get());
     2096  EXPECT_THAT(acd->codecs(), ElementsAreArray(kAudioCodecsAnswer));
     2097}
     2098
     2099// Test that a reanswer does not reuse video codecs from a previous media
     2100// section that is being recycled.
     2101TEST_F(MediaSessionDescriptionFactoryTest,
     2102       ReAnswerDoesNotReUseRecycledVideoCodecs) {
     2103  f1_.set_audio_codecs({}, {});
     2104  f2_.set_audio_codecs({}, {});
     2105
     2106  // Perform initial offer/answer in reverse (|f2_| as offerer) so that the
     2107  // second offer/answer is forward (|f1_| as offerer).
     2108  MediaSessionOptions opts;
     2109  AddMediaSection(MEDIA_TYPE_VIDEO, "v0", RtpTransceiverDirection::kSendRecv,
     2110                  kActive, &opts);
     2111  auto offer = absl::WrapUnique(f2_.CreateOffer(opts, nullptr));
     2112  auto answer = absl::WrapUnique(f1_.CreateAnswer(offer.get(), opts, nullptr));
     2113
     2114  // Recycle the media section by changing its mid.
     2115  opts.media_description_options[0].mid = "v1";
     2116  auto reoffer = absl::WrapUnique(f1_.CreateOffer(opts, answer.get()));
     2117  auto reanswer =
     2118      absl::WrapUnique(f2_.CreateAnswer(reoffer.get(), opts, offer.get()));
     2119
     2120  // Expect that the results of the first negotiation are ignored. If the m=
     2121  // section was not recycled the payload types would match the initial offerer.
     2122  const VideoContentDescription* vcd =
     2123      GetFirstVideoContentDescription(reanswer.get());
     2124  EXPECT_THAT(vcd->codecs(), ElementsAreArray(kVideoCodecsAnswer));
     2125}
     2126
    20232127// Create an updated offer after creating an answer to the original offer and
    20242128// verify that the codecs that were part of the original answer are not changed
  • trunk/Source/ThirdParty/libwebrtc/Source/webrtc/pc/peerconnection.cc

    r238996 r239136  
    39553955}
    39563956
     3957// Returns the ContentInfo at mline index |i|, or null if none exists.
     3958static const ContentInfo* GetContentByIndex(
     3959    const SessionDescriptionInterface* sdesc,
     3960    size_t i) {
     3961  if (!sdesc) {
     3962    return nullptr;
     3963  }
     3964  const ContentInfos& contents = sdesc->description()->contents();
     3965  return (i < contents.size() ? &contents[i] : nullptr);
     3966}
     3967
    39573968void PeerConnection::GetOptionsForUnifiedPlanOffer(
    39583969    const RTCOfferAnswerOptions& offer_answer_options,
     
    39823993    const ContentInfo* local_content =
    39833994        (i < local_contents.size() ? &local_contents[i] : nullptr);
     3995    const ContentInfo* current_local_content =
     3996        GetContentByIndex(current_local_description(), i);
    39843997    const ContentInfo* remote_content =
    39853998        (i < remote_contents.size() ? &remote_contents[i] : nullptr);
    3986     bool had_been_rejected = (local_content && local_content->rejected) ||
    3987                              (remote_content && remote_content->rejected);
     3999    const ContentInfo* current_remote_content =
     4000        GetContentByIndex(current_remote_description(), i);
     4001    bool had_been_rejected =
     4002        (current_local_content && current_local_content->rejected) ||
     4003        (current_remote_content && current_remote_content->rejected);
    39884004    const std::string& mid =
    39894005        (local_content ? local_content->name : remote_content->name);
     
    39964012      RTC_CHECK(transceiver);
    39974013      // A media section is considered eligible for recycling if it is marked as
    3998       // rejected in either the local or remote description.
     4014      // rejected in either the current local or current remote description.
    39994015      if (had_been_rejected && transceiver->stopped()) {
    40004016        session_options->media_description_options.push_back(
     
    41884204            cricket::MediaDescriptionOptions(
    41894205                cricket::MEDIA_TYPE_AUDIO, content.name,
    4190                 RtpTransceiverDirection::kInactive, true));
     4206                RtpTransceiverDirection::kInactive, /*stopped=*/true));
    41914207      } else {
     4208        bool stopped = (audio_direction == RtpTransceiverDirection::kInactive);
    41924209        session_options->media_description_options.push_back(
    4193             cricket::MediaDescriptionOptions(
    4194                 cricket::MEDIA_TYPE_AUDIO, content.name, audio_direction,
    4195                 audio_direction == RtpTransceiverDirection::kInactive));
     4210            cricket::MediaDescriptionOptions(cricket::MEDIA_TYPE_AUDIO,
     4211                                            content.name, audio_direction,
     4212                                             stopped));
    41964213        *audio_index = session_options->media_description_options.size() - 1;
    41974214      }
     
    42024219            cricket::MediaDescriptionOptions(
    42034220                cricket::MEDIA_TYPE_VIDEO, content.name,
    4204                 RtpTransceiverDirection::kInactive, true));
     4221                RtpTransceiverDirection::kInactive, /*stopped=*/true));
    42054222      } else {
     4223        bool stopped = (video_direction == RtpTransceiverDirection::kInactive);
    42064224        session_options->media_description_options.push_back(
    4207             cricket::MediaDescriptionOptions(
    4208                 cricket::MEDIA_TYPE_VIDEO, content.name, video_direction,
    4209                 video_direction == RtpTransceiverDirection::kInactive));
     4225            cricket::MediaDescriptionOptions(cricket::MEDIA_TYPE_VIDEO,
     4226                                            content.name, video_direction,
     4227                                             stopped));
    42104228        *video_index = session_options->media_description_options.size() - 1;
    42114229      }
  • trunk/Source/ThirdParty/libwebrtc/Source/webrtc/pc/peerconnection_jsep_unittest.cc

    r238967 r239136  
    701701// side are generated/updated correctly when recycling an audio/video media
    702702// section as a media section of either the same or opposite type.
     703// Correct recycling works as follows:
     704// - The m= section is re-offered with a new MID value and the new media type.
     705// - The previously-associated transceiver is dissociated when the new offer is
     706//   set as a local description on the offerer or as a remote description on
     707//   the answerer.
     708// - The new transceiver is associated with the new MID value.
    703709class RecycleMediaSectionTest
    704710    : public PeerConnectionJsepTest,
     
    715721};
    716722
    717 TEST_P(RecycleMediaSectionTest, VerifyOfferAnswerAndTransceivers) {
     723// Test that recycling works properly when a new transceiver recycles an m=
     724// section that was rejected in both the current local and remote descriptions.
     725TEST_P(RecycleMediaSectionTest, CurrentLocalAndCurrentRemoteRejected) {
    718726  auto caller = CreatePeerConnection();
    719727  auto first_transceiver = caller->AddTransceiver(first_type_);
     
    773781  ASSERT_EQ(2u, caller->pc()->GetTransceivers().size());
    774782  ASSERT_EQ(2u, callee->pc()->GetTransceivers().size());
     783}
     784
     785// Test that recycling works properly when a new transceiver recycles an m=
     786// section that was rejected in only the current remote description.
     787TEST_P(RecycleMediaSectionTest, CurrentRemoteOnlyRejected) {
     788  auto caller = CreatePeerConnection();
     789  auto caller_first_transceiver = caller->AddTransceiver(first_type_);
     790  auto callee = CreatePeerConnection();
     791
     792  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
     793
     794  std::string first_mid = *caller_first_transceiver->mid();
     795  ASSERT_EQ(1u, callee->pc()->GetTransceivers().size());
     796  auto callee_first_transceiver = callee->pc()->GetTransceivers()[0];
     797  callee_first_transceiver->Stop();
     798
     799  // The answer will have a rejected m= section.
     800  ASSERT_TRUE(
     801      caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
     802
     803  // The offer should reuse the previous media section but allocate a new MID
     804  // and change the media type.
     805  auto caller_second_transceiver = caller->AddTransceiver(second_type_);
     806  auto offer = caller->CreateOffer();
     807  const auto& offer_contents = offer->description()->contents();
     808  ASSERT_EQ(1u, offer_contents.size());
     809  EXPECT_FALSE(offer_contents[0].rejected);
     810  EXPECT_EQ(second_type_, offer_contents[0].media_description()->type());
     811  std::string second_mid = offer_contents[0].name;
     812  EXPECT_NE(first_mid, second_mid);
     813
     814  // Setting the local offer will dissociate the previous transceiver and set
     815  // the MID for the new transceiver.
     816  ASSERT_TRUE(
     817      caller->SetLocalDescription(CloneSessionDescription(offer.get())));
     818  EXPECT_EQ(absl::nullopt, caller_first_transceiver->mid());
     819  EXPECT_EQ(second_mid, caller_second_transceiver->mid());
     820
     821  // Setting the remote offer will dissociate the previous transceiver and
     822  // create a new transceiver for the media section.
     823  ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
     824  auto callee_transceivers = callee->pc()->GetTransceivers();
     825  ASSERT_EQ(2u, callee_transceivers.size());
     826  EXPECT_EQ(absl::nullopt, callee_transceivers[0]->mid());
     827  EXPECT_EQ(first_type_, callee_transceivers[0]->media_type());
     828  EXPECT_EQ(second_mid, callee_transceivers[1]->mid());
     829  EXPECT_EQ(second_type_, callee_transceivers[1]->media_type());
     830
     831  // The answer should have only one media section for the new transceiver.
     832  auto answer = callee->CreateAnswer();
     833  auto answer_contents = answer->description()->contents();
     834  ASSERT_EQ(1u, answer_contents.size());
     835  EXPECT_FALSE(answer_contents[0].rejected);
     836  EXPECT_EQ(second_mid, answer_contents[0].name);
     837  EXPECT_EQ(second_type_, answer_contents[0].media_description()->type());
     838
     839  // Setting the local answer should succeed.
     840  ASSERT_TRUE(
     841      callee->SetLocalDescription(CloneSessionDescription(answer.get())));
     842
     843  // Setting the remote answer should succeed and not create any new
     844  // transceivers.
     845  ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer)));
     846  ASSERT_EQ(2u, caller->pc()->GetTransceivers().size());
     847  ASSERT_EQ(2u, callee->pc()->GetTransceivers().size());
     848}
     849
     850// Test that recycling works properly when a new transceiver recycles an m=
     851// section that was rejected only in the current local description.
     852TEST_P(RecycleMediaSectionTest, CurrentLocalOnlyRejected) {
     853  auto caller = CreatePeerConnection();
     854  auto caller_first_transceiver = caller->AddTransceiver(first_type_);
     855  auto callee = CreatePeerConnection();
     856
     857  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
     858
     859  std::string first_mid = *caller_first_transceiver->mid();
     860  ASSERT_EQ(1u, callee->pc()->GetTransceivers().size());
     861  auto callee_first_transceiver = callee->pc()->GetTransceivers()[0];
     862  callee_first_transceiver->Stop();
     863
     864  // The answer will have a rejected m= section.
     865  ASSERT_TRUE(
     866      caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
     867
     868  // The offer should reuse the previous media section but allocate a new MID
     869  // and change the media type.
     870  auto callee_second_transceiver = callee->AddTransceiver(second_type_);
     871  auto offer = callee->CreateOffer();
     872  const auto& offer_contents = offer->description()->contents();
     873  ASSERT_EQ(1u, offer_contents.size());
     874  EXPECT_FALSE(offer_contents[0].rejected);
     875  EXPECT_EQ(second_type_, offer_contents[0].media_description()->type());
     876  std::string second_mid = offer_contents[0].name;
     877  EXPECT_NE(first_mid, second_mid);
     878
     879  // Setting the local offer will dissociate the previous transceiver and set
     880  // the MID for the new transceiver.
     881  ASSERT_TRUE(
     882      callee->SetLocalDescription(CloneSessionDescription(offer.get())));
     883  EXPECT_EQ(absl::nullopt, callee_first_transceiver->mid());
     884  EXPECT_EQ(second_mid, callee_second_transceiver->mid());
     885
     886  // Setting the remote offer will dissociate the previous transceiver and
     887  // create a new transceiver for the media section.
     888  ASSERT_TRUE(caller->SetRemoteDescription(std::move(offer)));
     889  auto caller_transceivers = caller->pc()->GetTransceivers();
     890  ASSERT_EQ(2u, caller_transceivers.size());
     891  EXPECT_EQ(absl::nullopt, caller_transceivers[0]->mid());
     892  EXPECT_EQ(first_type_, caller_transceivers[0]->media_type());
     893  EXPECT_EQ(second_mid, caller_transceivers[1]->mid());
     894  EXPECT_EQ(second_type_, caller_transceivers[1]->media_type());
     895
     896  // The answer should have only one media section for the new transceiver.
     897  auto answer = caller->CreateAnswer();
     898  auto answer_contents = answer->description()->contents();
     899  ASSERT_EQ(1u, answer_contents.size());
     900  EXPECT_FALSE(answer_contents[0].rejected);
     901  EXPECT_EQ(second_mid, answer_contents[0].name);
     902  EXPECT_EQ(second_type_, answer_contents[0].media_description()->type());
     903
     904  // Setting the local answer should succeed.
     905  ASSERT_TRUE(
     906      caller->SetLocalDescription(CloneSessionDescription(answer.get())));
     907
     908  // Setting the remote answer should succeed and not create any new
     909  // transceivers.
     910  ASSERT_TRUE(callee->SetRemoteDescription(std::move(answer)));
     911  ASSERT_EQ(2u, callee->pc()->GetTransceivers().size());
     912  ASSERT_EQ(2u, caller->pc()->GetTransceivers().size());
     913}
     914
     915// Test that a m= section is *not* recycled if the media section is only
     916// rejected in the pending local description and there is no current remote
     917// description.
     918TEST_P(RecycleMediaSectionTest, PendingLocalRejectedAndNoRemote) {
     919  auto caller = CreatePeerConnection();
     920  auto caller_first_transceiver = caller->AddTransceiver(first_type_);
     921
     922  ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
     923
     924  std::string first_mid = *caller_first_transceiver->mid();
     925  caller_first_transceiver->Stop();
     926
     927  // The reoffer will have a rejected m= section.
     928  ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
     929
     930  auto caller_second_transceiver = caller->AddTransceiver(second_type_);
     931
     932  // The reoffer should not recycle the existing m= section since it is not
     933  // rejected in either the *current* local or *current* remote description.
     934  auto reoffer = caller->CreateOffer();
     935  auto reoffer_contents = reoffer->description()->contents();
     936  ASSERT_EQ(2u, reoffer_contents.size());
     937  EXPECT_TRUE(reoffer_contents[0].rejected);
     938  EXPECT_EQ(first_type_, reoffer_contents[0].media_description()->type());
     939  EXPECT_EQ(first_mid, reoffer_contents[0].name);
     940  EXPECT_FALSE(reoffer_contents[1].rejected);
     941  EXPECT_EQ(second_type_, reoffer_contents[1].media_description()->type());
     942  std::string second_mid = reoffer_contents[1].name;
     943  EXPECT_NE(first_mid, second_mid);
     944
     945  ASSERT_TRUE(caller->SetLocalDescription(std::move(reoffer)));
     946
     947  // Both RtpTransceivers are associated.
     948  EXPECT_EQ(first_mid, caller_first_transceiver->mid());
     949  EXPECT_EQ(second_mid, caller_second_transceiver->mid());
     950}
     951
     952// Test that a m= section is *not* recycled if the media section is only
     953// rejected in the pending local description and not rejected in the current
     954// remote description.
     955TEST_P(RecycleMediaSectionTest, PendingLocalRejectedAndNotRejectedRemote) {
     956  auto caller = CreatePeerConnection();
     957  auto caller_first_transceiver = caller->AddTransceiver(first_type_);
     958  auto callee = CreatePeerConnection();
     959
     960  ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
     961
     962  std::string first_mid = *caller_first_transceiver->mid();
     963  caller_first_transceiver->Stop();
     964
     965  // The reoffer will have a rejected m= section.
     966  ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
     967
     968  auto caller_second_transceiver = caller->AddTransceiver(second_type_);
     969
     970  // The reoffer should not recycle the existing m= section since it is not
     971  // rejected in either the *current* local or *current* remote description.
     972  auto reoffer = caller->CreateOffer();
     973  auto reoffer_contents = reoffer->description()->contents();
     974  ASSERT_EQ(2u, reoffer_contents.size());
     975  EXPECT_TRUE(reoffer_contents[0].rejected);
     976  EXPECT_EQ(first_type_, reoffer_contents[0].media_description()->type());
     977  EXPECT_EQ(first_mid, reoffer_contents[0].name);
     978  EXPECT_FALSE(reoffer_contents[1].rejected);
     979  EXPECT_EQ(second_type_, reoffer_contents[1].media_description()->type());
     980  std::string second_mid = reoffer_contents[1].name;
     981  EXPECT_NE(first_mid, second_mid);
     982
     983  ASSERT_TRUE(caller->SetLocalDescription(std::move(reoffer)));
     984
     985  // Both RtpTransceivers are associated.
     986  EXPECT_EQ(first_mid, caller_first_transceiver->mid());
     987  EXPECT_EQ(second_mid, caller_second_transceiver->mid());
     988}
     989
     990// Test that an m= section is *not* recycled if the media section is only
     991// rejected in the pending remote description and there is no current local
     992// description.
     993TEST_P(RecycleMediaSectionTest, PendingRemoteRejectedAndNoLocal) {
     994  auto caller = CreatePeerConnection();
     995  auto caller_first_transceiver = caller->AddTransceiver(first_type_);
     996  auto callee = CreatePeerConnection();
     997
     998  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
     999
     1000  ASSERT_EQ(1u, callee->pc()->GetTransceivers().size());
     1001  auto callee_first_transceiver = callee->pc()->GetTransceivers()[0];
     1002  std::string first_mid = *callee_first_transceiver->mid();
     1003  caller_first_transceiver->Stop();
     1004
     1005  // The reoffer will have a rejected m= section.
     1006  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
     1007
     1008  auto callee_second_transceiver = callee->AddTransceiver(second_type_);
     1009
     1010  // The reoffer should not recycle the existing m= section since it is not
     1011  // rejected in either the *current* local or *current* remote description.
     1012  auto reoffer = callee->CreateOffer();
     1013  auto reoffer_contents = reoffer->description()->contents();
     1014  ASSERT_EQ(2u, reoffer_contents.size());
     1015  EXPECT_TRUE(reoffer_contents[0].rejected);
     1016  EXPECT_EQ(first_type_, reoffer_contents[0].media_description()->type());
     1017  EXPECT_EQ(first_mid, reoffer_contents[0].name);
     1018  EXPECT_FALSE(reoffer_contents[1].rejected);
     1019  EXPECT_EQ(second_type_, reoffer_contents[1].media_description()->type());
     1020  std::string second_mid = reoffer_contents[1].name;
     1021  EXPECT_NE(first_mid, second_mid);
     1022
     1023  // Note: Cannot actually set the reoffer since the callee is in the signaling
     1024  // state 'have-remote-offer'.
     1025}
     1026
     1027// Test that an m= section is *not* recycled if the media section is only
     1028// rejected in the pending remote description and not rejected in the current
     1029// local description.
     1030TEST_P(RecycleMediaSectionTest, PendingRemoteRejectedAndNotRejectedLocal) {
     1031  auto caller = CreatePeerConnection();
     1032  auto caller_first_transceiver = caller->AddTransceiver(first_type_);
     1033  auto callee = CreatePeerConnection();
     1034
     1035  ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
     1036
     1037  ASSERT_EQ(1u, callee->pc()->GetTransceivers().size());
     1038  auto callee_first_transceiver = callee->pc()->GetTransceivers()[0];
     1039  std::string first_mid = *callee_first_transceiver->mid();
     1040  caller_first_transceiver->Stop();
     1041
     1042  // The reoffer will have a rejected m= section.
     1043  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
     1044
     1045  auto callee_second_transceiver = callee->AddTransceiver(second_type_);
     1046
     1047  // The reoffer should not recycle the existing m= section since it is not
     1048  // rejected in either the *current* local or *current* remote description.
     1049  auto reoffer = callee->CreateOffer();
     1050  auto reoffer_contents = reoffer->description()->contents();
     1051  ASSERT_EQ(2u, reoffer_contents.size());
     1052  EXPECT_TRUE(reoffer_contents[0].rejected);
     1053  EXPECT_EQ(first_type_, reoffer_contents[0].media_description()->type());
     1054  EXPECT_EQ(first_mid, reoffer_contents[0].name);
     1055  EXPECT_FALSE(reoffer_contents[1].rejected);
     1056  EXPECT_EQ(second_type_, reoffer_contents[1].media_description()->type());
     1057  std::string second_mid = reoffer_contents[1].name;
     1058  EXPECT_NE(first_mid, second_mid);
     1059
     1060  // Note: Cannot actually set the reoffer since the callee is in the signaling
     1061  // state 'have-remote-offer'.
    7751062}
    7761063
Note: See TracChangeset for help on using the changeset viewer.