Move network route change handling to RtpTransceiver

Relocate the logic for subscribing to and processing network route
changes from BaseChannel to RtpTransceiver. This adds a transport
pointer to RtpTransceiver, a part of moving channel logic into the
transceiver class.

As part of this, update the transport internal classes to accept a
TaskQueueBase* during construction to allow for more explicit
initialization of network sequence checkers.

The transport lookup callback used during channel creation is also
simplified by removing the redundant media ID parameter, as the context
is now captured within the provided lambda.

Bug: webrtc:42224170
Change-Id: I783178885df8bcf3b9ce0a13ac3b64aa023d62f0
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/467160
Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#47583}
diff --git a/p2p/BUILD.gn b/p2p/BUILD.gn
index 81f4b17..a3993ad 100644
--- a/p2p/BUILD.gn
+++ b/p2p/BUILD.gn
@@ -326,6 +326,7 @@
     "../api:dtls_transport_interface",
     "../api:rtc_error",
     "../api:scoped_refptr",
+    "../api/task_queue",
     "../rtc_base:buffer",
     "../rtc_base:callback_list",
     "../rtc_base:checks",
@@ -413,6 +414,7 @@
     "../api:candidate",
     "../api:peer_connection_interface",
     "../api:rtc_error",
+    "../api/task_queue",
     "../api/transport:enums",
     "../api/units:time_delta",
     "../rtc_base:callback_list",
@@ -519,6 +521,7 @@
   ]
   deps = [
     "../api:sequence_checker",
+    "../api/task_queue",
     "../rtc_base:async_packet_socket",
     "../rtc_base:callback_list",
     "../rtc_base:checks",
diff --git a/p2p/base/ice_transport_internal.cc b/p2p/base/ice_transport_internal.cc
index e41fa99..000abfd 100644
--- a/p2p/base/ice_transport_internal.cc
+++ b/p2p/base/ice_transport_internal.cc
@@ -19,8 +19,10 @@
 #include "api/candidate.h"
 #include "api/peer_connection_interface.h"
 #include "api/rtc_error.h"
+#include "api/task_queue/task_queue_base.h"
 #include "api/units/time_delta.h"
 #include "p2p/base/p2p_constants.h"
+#include "p2p/base/packet_transport_internal.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/net_helper.h"
 
@@ -234,7 +236,8 @@
   return RTCError::OK();
 }
 
-IceTransportInternal::IceTransportInternal() = default;
+IceTransportInternal::IceTransportInternal(TaskQueueBase* attached_queue)
+    : PacketTransportInternal(attached_queue) {}
 
 IceTransportInternal::~IceTransportInternal() = default;
 
diff --git a/p2p/base/ice_transport_internal.h b/p2p/base/ice_transport_internal.h
index a30db71..ef7d1f3 100644
--- a/p2p/base/ice_transport_internal.h
+++ b/p2p/base/ice_transport_internal.h
@@ -23,6 +23,7 @@
 #include "api/candidate.h"
 #include "api/peer_connection_interface.h"
 #include "api/rtc_error.h"
+#include "api/task_queue/task_queue_base.h"
 #include "api/transport/enums.h"
 #include "api/units/time_delta.h"
 #include "p2p/base/candidate_pair_interface.h"
@@ -251,7 +252,7 @@
 // TODO(bugs.webrtc.org/15609): Define a public API for this.
 class RTC_EXPORT IceTransportInternal : public PacketTransportInternal {
  public:
-  IceTransportInternal();
+  explicit IceTransportInternal(TaskQueueBase* attached_queue = nullptr);
   ~IceTransportInternal() override;
 
   // This class is uncopyable and immovable.
diff --git a/p2p/base/packet_transport_internal.cc b/p2p/base/packet_transport_internal.cc
index 1e44151..7fbad1c 100644
--- a/p2p/base/packet_transport_internal.cc
+++ b/p2p/base/packet_transport_internal.cc
@@ -15,6 +15,7 @@
 
 #include "absl/functional/any_invocable.h"
 #include "api/sequence_checker.h"
+#include "api/task_queue/task_queue_base.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/network/received_packet.h"
 #include "rtc_base/network_route.h"
@@ -22,7 +23,8 @@
 
 namespace webrtc {
 
-PacketTransportInternal::PacketTransportInternal() = default;
+PacketTransportInternal::PacketTransportInternal(TaskQueueBase* attached_queue)
+    : network_checker_(attached_queue) {}
 
 PacketTransportInternal::~PacketTransportInternal() = default;
 
diff --git a/p2p/base/packet_transport_internal.h b/p2p/base/packet_transport_internal.h
index 93b6977..e113d60 100644
--- a/p2p/base/packet_transport_internal.h
+++ b/p2p/base/packet_transport_internal.h
@@ -18,6 +18,7 @@
 
 #include "absl/functional/any_invocable.h"
 #include "api/sequence_checker.h"
+#include "api/task_queue/task_queue_base.h"
 #include "rtc_base/async_packet_socket.h"
 #include "rtc_base/callback_list.h"
 #include "rtc_base/network/received_packet.h"
@@ -130,7 +131,7 @@
   virtual ~PacketTransportInternal();
 
  protected:
-  PacketTransportInternal();
+  explicit PacketTransportInternal(TaskQueueBase* attached_queue = nullptr);
 
   void NotifyPacketReceived(const ReceivedIpPacket& packet);
   void NotifyOnClose();
diff --git a/p2p/dtls/dtls_transport_internal.cc b/p2p/dtls/dtls_transport_internal.cc
index 3e3387f..00b68d0 100644
--- a/p2p/dtls/dtls_transport_internal.cc
+++ b/p2p/dtls/dtls_transport_internal.cc
@@ -10,9 +10,13 @@
 
 #include "p2p/dtls/dtls_transport_internal.h"
 
+#include "api/task_queue/task_queue_base.h"
+#include "p2p/base/packet_transport_internal.h"
+
 namespace webrtc {
 
-DtlsTransportInternal::DtlsTransportInternal() = default;
+DtlsTransportInternal::DtlsTransportInternal(TaskQueueBase* attached_queue)
+    : PacketTransportInternal(attached_queue) {}
 
 DtlsTransportInternal::~DtlsTransportInternal() = default;
 
diff --git a/p2p/dtls/dtls_transport_internal.h b/p2p/dtls/dtls_transport_internal.h
index b06f39e..df287ee 100644
--- a/p2p/dtls/dtls_transport_internal.h
+++ b/p2p/dtls/dtls_transport_internal.h
@@ -22,6 +22,7 @@
 #include "api/dtls_transport_interface.h"
 #include "api/rtc_error.h"
 #include "api/scoped_refptr.h"
+#include "api/task_queue/task_queue_base.h"
 #include "p2p/base/ice_transport_internal.h"
 #include "p2p/base/packet_transport_internal.h"
 #include "rtc_base/buffer.h"
@@ -157,7 +158,7 @@
   }
 
  protected:
-  DtlsTransportInternal();
+  explicit DtlsTransportInternal(TaskQueueBase* attached_queue = nullptr);
 
  private:
   CallbackList<const SSLHandshakeError> dtls_handshake_error_callback_list_;
diff --git a/p2p/dtls/fake_dtls_transport.h b/p2p/dtls/fake_dtls_transport.h
index 0beb398..bec2ea2 100644
--- a/p2p/dtls/fake_dtls_transport.h
+++ b/p2p/dtls/fake_dtls_transport.h
@@ -69,7 +69,8 @@
         });
   }
   explicit FakeDtlsTransport(std::unique_ptr<FakeIceTransportInternal> ice)
-      : owned_ice_transport_(std::move(ice)),
+      : DtlsTransportInternal(ice->network_thread()),
+        owned_ice_transport_(std::move(ice)),
         transport_name_(owned_ice_transport_->transport_name()),
         component_(owned_ice_transport_->component()),
         dtls_fingerprint_("", std::span<const uint8_t>()) {
diff --git a/p2p/test/fake_ice_transport.h b/p2p/test/fake_ice_transport.h
index 4163af5..f41e324 100644
--- a/p2p/test/fake_ice_transport.h
+++ b/p2p/test/fake_ice_transport.h
@@ -63,7 +63,8 @@
                                     int component,
                                     TaskQueueBase* network_thread = nullptr,
                                     absl::string_view field_trials_string = "")
-      : name_(name),
+      : IceTransportInternal(network_thread),
+        name_(name),
         component_(component),
         network_thread_(network_thread ? network_thread
                                        : TaskQueueBase::Current()),
@@ -187,6 +188,8 @@
     return remote_candidates_;
   }
 
+  TaskQueueBase* network_thread() const { return network_thread_; }
+
   // Fake IceTransportInternal implementation.
   const std::string& transport_name() const override { return name_; }
   int component() const override { return component_; }
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 8126afd..1f0b54e 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -1744,6 +1744,7 @@
     "../rtc_base:crypto_random",
     "../rtc_base:logging",
     "../rtc_base:macromagic",
+    "../rtc_base:network_route",
     "../rtc_base:threading",
     "../rtc_base/system:plan_b_only",
     "//third_party/abseil-cpp/absl/algorithm:container",
@@ -3508,15 +3509,20 @@
       "../api/environment",
       "../api/video_codecs:scalability_mode",
       "../api/video_codecs:video_codecs_api",
+      "../call:call_interfaces",
       "../media:codec",
       "../media:media_channel",
       "../media:rtc_media_config",
       "../media:rtc_media_tests_utils",
       "../p2p:p2p_test_utils",
+      "../rtc_base:checks",
+      "../rtc_base:logging",
+      "../rtc_base:network_route",
       "../rtc_base:threading",
       "../test:create_test_environment",
       "../test:run_loop",
       "../test:test_support",
+      "//third_party/abseil-cpp/absl/functional:any_invocable",
       "//third_party/abseil-cpp/absl/strings:string_view",
     ]
   }
diff --git a/pc/channel.cc b/pc/channel.cc
index 3a35007..d1d7f01 100644
--- a/pc/channel.cc
+++ b/pc/channel.cc
@@ -49,7 +49,6 @@
 #include "rtc_base/copy_on_write_buffer.h"
 #include "rtc_base/logging.h"
 #include "rtc_base/network/sent_packet.h"
-#include "rtc_base/network_route.h"
 #include "rtc_base/socket.h"
 #include "rtc_base/strings/string_format.h"
 #include "rtc_base/thread.h"
@@ -245,10 +244,6 @@
   rtp_transport_ = rtp_transport;
   rtp_transport_->SubscribeReadyToSend(
       this, [this](bool ready) { OnTransportReadyToSend(ready); });
-  rtp_transport_->SubscribeNetworkRouteChanged(
-      this, [this](std::optional<NetworkRoute> route) {
-        OnNetworkRouteChanged(route);
-      });
   rtp_transport_->SubscribeWritableState(
       this, [this](bool state) { OnWritableState(state); });
   rtp_transport_->SubscribeSentPacket(
@@ -262,7 +257,6 @@
   RTC_DCHECK(media_send_channel());
   rtp_transport_->UnregisterRtpDemuxerSink(this);
   rtp_transport_->UnsubscribeReadyToSend(this);
-  rtp_transport_->UnsubscribeNetworkRouteChanged(this);
   rtp_transport_->UnsubscribeWritableState(this);
   rtp_transport_->UnsubscribeSentPacket(this);
   rtp_transport_ = nullptr;
@@ -394,24 +388,6 @@
   }
 }
 
-void BaseChannel::OnNetworkRouteChanged(
-    std::optional<NetworkRoute> network_route) {
-  RTC_DCHECK_RUN_ON(network_thread());
-  RTC_DCHECK(network_initialized());
-
-  RTC_LOG(LS_INFO) << "Network route changed for " << ToString();
-
-  NetworkRoute new_route;
-  if (network_route) {
-    new_route = *(network_route);
-  }
-  // Note: When the RTCP-muxing is not enabled, RTCP transport and RTP transport
-  // use the same transport name and MediaChannel::OnNetworkRouteChanged cannot
-  // work correctly. Intentionally leave it broken to simplify the code and
-  // encourage the users to stop using non-muxing RTCP.
-  media_send_channel()->OnNetworkRouteChanged(transport_name(), new_route);
-}
-
 void BaseChannel::OnTransportReadyToSend(bool ready) {
   RTC_DCHECK_RUN_ON(network_thread());
   RTC_DCHECK(network_initialized());
diff --git a/pc/channel.h b/pc/channel.h
index 5dad62e..f3d5815 100644
--- a/pc/channel.h
+++ b/pc/channel.h
@@ -45,7 +45,6 @@
 #include "rtc_base/containers/flat_set.h"
 #include "rtc_base/copy_on_write_buffer.h"
 #include "rtc_base/network/sent_packet.h"
-#include "rtc_base/network_route.h"
 #include "rtc_base/socket.h"
 #include "rtc_base/thread.h"
 #include "rtc_base/thread_annotations.h"
@@ -219,8 +218,6 @@
   // From RtpTransportInternal
   void OnWritableState(bool writable);
 
-  void OnNetworkRouteChanged(std::optional<NetworkRoute> network_route);
-
   bool SendPacket(bool rtcp,
                   CopyOnWriteBuffer* packet,
                   const AsyncSocketPacketOptions& options);
diff --git a/pc/channel_unittest.cc b/pc/channel_unittest.cc
index 11bebc3..38510bd 100644
--- a/pc/channel_unittest.cc
+++ b/pc/channel_unittest.cc
@@ -40,7 +40,6 @@
 #include "media/base/rid_description.h"
 #include "media/base/stream_params.h"
 #include "p2p/base/candidate_pair_interface.h"
-#include "p2p/base/ice_transport_internal.h"
 #include "p2p/base/p2p_constants.h"
 #include "p2p/base/packet_transport_internal.h"
 #include "p2p/dtls/dtls_transport_internal.h"
@@ -54,7 +53,6 @@
 #include "rtc_base/buffer.h"
 #include "rtc_base/byte_order.h"
 #include "rtc_base/checks.h"
-#include "rtc_base/network_route.h"
 #include "rtc_base/rtc_certificate.h"
 #include "rtc_base/socket.h"
 #include "rtc_base/ssl_identity.h"
@@ -1088,69 +1086,6 @@
     EXPECT_TRUE(media_send_channel1_impl()->sending());
   }
 
-  // Tests that when the transport channel signals a candidate pair change
-  // event, the media channel will receive a call on the network route change.
-  void TestNetworkRouteChanges() {
-    static constexpr uint16_t kLocalNetId = 1;
-    static constexpr uint16_t kRemoteNetId = 2;
-    static constexpr int kLastPacketId = 100;
-    // Ipv4(20) + UDP(8).
-    static constexpr int kTransportOverheadPerPacket = 28;
-    static constexpr int kSrtpOverheadPerPacket = 10;
-
-    CreateChannels(DTLS, DTLS);
-    SendInitiate();
-
-    typename T::MediaSendChannel* media_send_channel1_impl =
-        this->media_send_channel1_impl();
-    ASSERT_TRUE(media_send_channel1_impl);
-
-    // Need to wait for the threads before calling
-    // `set_num_network_route_changes` because the network route would be set
-    // when creating the channel.
-    WaitForThreads();
-    media_send_channel1_impl->set_num_network_route_changes(0);
-    SendTask(network_thread_, [this] {
-      webrtc::NetworkRoute network_route;
-      // The transport channel becomes disconnected.
-      fake_rtp_dtls_transport1_->ice_transport()->NotifyNetworkRouteChanged(
-          std::optional<webrtc::NetworkRoute>(network_route));
-    });
-    WaitForThreads();
-    EXPECT_EQ(1, media_send_channel1_impl->num_network_route_changes());
-    EXPECT_FALSE(media_send_channel1_impl->last_network_route().connected);
-    media_send_channel1_impl->set_num_network_route_changes(0);
-
-    SendTask(network_thread_, [this] {
-      webrtc::NetworkRoute network_route;
-      network_route.connected = true;
-      network_route.local =
-          webrtc::RouteEndpoint::CreateWithNetworkId(kLocalNetId);
-      network_route.remote =
-          webrtc::RouteEndpoint::CreateWithNetworkId(kRemoteNetId);
-      network_route.last_sent_packet_id = kLastPacketId;
-      network_route.packet_overhead = kTransportOverheadPerPacket;
-      // The transport channel becomes connected.
-      fake_rtp_dtls_transport1_->ice_transport()->NotifyNetworkRouteChanged(
-
-          std::optional<webrtc::NetworkRoute>(network_route));
-    });
-    WaitForThreads();
-    EXPECT_EQ(1, media_send_channel1_impl->num_network_route_changes());
-    EXPECT_TRUE(media_send_channel1_impl->last_network_route().connected);
-    EXPECT_EQ(
-        kLocalNetId,
-        media_send_channel1_impl->last_network_route().local.network_id());
-    EXPECT_EQ(
-        kRemoteNetId,
-        media_send_channel1_impl->last_network_route().remote.network_id());
-    EXPECT_EQ(
-        kLastPacketId,
-        media_send_channel1_impl->last_network_route().last_sent_packet_id);
-    EXPECT_EQ(kTransportOverheadPerPacket + kSrtpOverheadPerPacket,
-              media_send_channel1_impl->transport_overhead_per_packet());
-  }
-
   // Test setting up a call.
   void TestCallSetup() {
     CreateChannels(0, 0);
@@ -1901,10 +1836,6 @@
   Base::TestMediaContentDirection();
 }
 
-TEST_F(VoiceChannelSingleThreadTest, TestNetworkRouteChanges) {
-  Base::TestNetworkRouteChanges();
-}
-
 TEST_F(VoiceChannelSingleThreadTest, TestCallSetup) {
   Base::TestCallSetup();
 }
@@ -2067,10 +1998,6 @@
   Base::TestMediaContentDirection();
 }
 
-TEST_F(VoiceChannelDoubleThreadTest, TestNetworkRouteChanges) {
-  Base::TestNetworkRouteChanges();
-}
-
 TEST_F(VoiceChannelDoubleThreadTest, TestCallSetup) {
   Base::TestCallSetup();
 }
@@ -2202,10 +2129,6 @@
   Base::TestMediaContentDirection();
 }
 
-TEST_F(VideoChannelSingleThreadTest, TestNetworkRouteChanges) {
-  Base::TestNetworkRouteChanges();
-}
-
 TEST_F(VideoChannelSingleThreadTest, TestCallSetup) {
   Base::TestCallSetup();
 }
@@ -2654,10 +2577,6 @@
   Base::TestMediaContentDirection();
 }
 
-TEST_F(VideoChannelDoubleThreadTest, TestNetworkRouteChanges) {
-  Base::TestNetworkRouteChanges();
-}
-
 TEST_F(VideoChannelDoubleThreadTest, TestCallSetup) {
   Base::TestCallSetup();
 }
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index 1b4e0c2..3d7b01d 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -3107,7 +3107,7 @@
          rtp_manager()->transceivers()->UnsafeList()) {
       auto internal = transceiver->internal();
       if (internal->HasChannel() && internal->mid() == mid) {
-        ret = internal->SetChannelRtpTransport(rtp_transport);
+        ret = internal->SetRtpTransport(rtp_transport);
       }
     }
   }
diff --git a/pc/rtp_transceiver.cc b/pc/rtp_transceiver.cc
index c8bf45c..9554f20 100644
--- a/pc/rtp_transceiver.cc
+++ b/pc/rtp_transceiver.cc
@@ -70,6 +70,7 @@
 #include "rtc_base/checks.h"
 #include "rtc_base/crypto_random.h"
 #include "rtc_base/logging.h"
+#include "rtc_base/network_route.h"
 #include "rtc_base/system/plan_b_only.h"
 #include "rtc_base/thread.h"
 
@@ -464,8 +465,7 @@
     const AudioOptions& audio_options,
     const VideoOptions& video_options,
     VideoBitrateAllocatorFactory* video_bitrate_allocator_factory,
-    absl::AnyInvocable<RtpTransportInternal*(absl::string_view) &&>
-        transport_lookup,
+    absl::AnyInvocable<RtpTransportInternal*() &&> transport_lookup,
     ScopedOperationsBatcher& worker_tasks,
     ScopedOperationsBatcher& network_tasks) {
   RTC_DCHECK_RUN_ON(thread_);
@@ -562,18 +562,11 @@
       [this, transport_lookup = std::move(transport_lookup)]() mutable
           -> RTCErrorOr<ScopedOperationsBatcher::FinalizerTask> {
         RTC_DCHECK_RUN_ON(context()->network_thread());
-        auto* channel = channel_.get();
-        RTC_DCHECK(channel);
-        RtpTransportInternal* transport =
-            std::move(transport_lookup)(channel->mid());
-        if (!channel->SetRtpTransport(transport)) {
-          return RTCError::InvalidParameter()
-                 << "Invalid transport for mid=" << channel->mid();
+        auto result = InitializeOnNetworkThread(std::move(transport_lookup));
+        if (!result.ok()) {
+          return result.MoveError();
         }
-        std::optional<std::string> transport_name;
-        if (transport) {
-          transport_name = transport->transport_name();
-        }
+        std::optional<std::string> transport_name = std::move(result.value());
         return ScopedOperationsBatcher::FinalizerTask(
             [this, transport_name = std::move(transport_name)]() mutable {
               RTC_DCHECK_RUN_ON(thread_);
@@ -584,8 +577,7 @@
 
 RTCError RtpTransceiver::SetChannelForTest(
     std::unique_ptr<ChannelInterface> channel,
-    absl::AnyInvocable<RtpTransportInternal*(const std::string&) &&>
-        transport_lookup) {
+    absl::AnyInvocable<RtpTransportInternal*() &&> transport_lookup) {
   RTC_DCHECK_RUN_ON(thread_);
   RTC_DCHECK(channel);
   RTC_DCHECK(transport_lookup);
@@ -614,16 +606,12 @@
   std::optional<std::string> transport_name;
   RTCError err = context()->network_thread()->BlockingCall(
       [&, flag = signaling_thread_safety_, channel = channel_.get()]() {
-        RtpTransportInternal* transport =
-            std::move(transport_lookup)(channel->mid());
-        if (!channel->SetRtpTransport(transport)) {
-          return RTCError::InvalidParameter()
-                 << "Invalid transport for mid=" << channel->mid();
+        RTC_DCHECK_RUN_ON(context()->network_thread());
+        auto result = InitializeOnNetworkThread(std::move(transport_lookup));
+        if (result.ok()) {
+          transport_name = std::move(result.value());
         }
-        if (transport) {
-          transport_name = transport->transport_name();
-        }
-        return RTCError::OK();
+        return result.MoveError();
       });
 
   if (err.ok()) {
@@ -650,9 +638,11 @@
   signaling_thread_safety_ = nullptr;
 
   ChannelInterface* channel = channel_.get();
-  return [channel, flag = network_thread_safety_] {
+  return [this, channel, flag = network_thread_safety_] {
+    RTC_DCHECK_RUN_ON(context()->network_thread());
     flag->SetNotAlive();
     channel->SetRtpTransport(nullptr);
+    ClearRtpTransportState();
   };
 }
 
@@ -1377,11 +1367,73 @@
   }
 }
 
-bool RtpTransceiver::SetChannelRtpTransport(
-    RtpTransportInternal* rtp_transport) {
+bool RtpTransceiver::SetRtpTransport(RtpTransportInternal* transport) {
   RTC_DCHECK_RUN_ON(context()->network_thread());
   RTC_DCHECK(channel_);
-  return channel_->SetRtpTransport(rtp_transport);
+
+  if (transport == rtp_transport_) {
+    return true;
+  }
+
+  ClearRtpTransportState();
+
+  if (!channel_->SetRtpTransport(transport)) {
+    return false;
+  }
+
+  SetRtpTransportState(transport);
+  return true;
+}
+
+void RtpTransceiver::ClearRtpTransportState() {
+  RTC_DCHECK_RUN_ON(context()->network_thread());
+  if (rtp_transport_) {
+    rtp_transport_->UnsubscribeNetworkRouteChanged(this);
+    rtp_transport_ = nullptr;
+  }
+}
+
+void RtpTransceiver::SetRtpTransportState(RtpTransportInternal* transport) {
+  RTC_DCHECK_RUN_ON(context()->network_thread());
+  RTC_DCHECK(!rtp_transport_);
+  rtp_transport_ = transport;
+  if (rtp_transport_) {
+    rtp_transport_->SubscribeNetworkRouteChanged(
+        this, [this](std::optional<NetworkRoute> route) {
+          RTC_DCHECK_RUN_ON(context()->network_thread());
+          OnNetworkRouteChanged(route);
+        });
+  }
+}
+
+RTCErrorOr<std::optional<std::string>>
+RtpTransceiver::InitializeOnNetworkThread(
+    absl::AnyInvocable<RtpTransportInternal*() &&> transport_lookup) {
+  RTC_DCHECK_RUN_ON(context()->network_thread());
+  RTC_DCHECK(!rtp_transport_);
+  RTC_DCHECK(channel_);
+  RtpTransportInternal* transport = std::move(transport_lookup)();
+  if (!SetRtpTransport(transport)) {
+    return RTCError::InvalidParameter()
+           << "Invalid transport for mid=" << channel_->mid();
+  }
+
+  std::optional<std::string> transport_name;
+  if (transport) {
+    transport_name = transport->transport_name();
+  }
+  return transport_name;
+}
+
+void RtpTransceiver::OnNetworkRouteChanged(
+    std::optional<NetworkRoute> network_route) {
+  RTC_DCHECK_RUN_ON(context()->network_thread());
+  if (channel_ && rtp_transport_) {
+    RTC_LOG(LS_INFO) << "Network route changed for mid=" << channel_->mid();
+    channel_->media_send_channel()->OnNetworkRouteChanged(
+        rtp_transport_->transport_name(),
+        network_route.value_or(NetworkRoute()));
+  }
 }
 
 void RtpTransceiver::SetChannelLocalContent(
diff --git a/pc/rtp_transceiver.h b/pc/rtp_transceiver.h
index ee39dc9..6ffbe33 100644
--- a/pc/rtp_transceiver.h
+++ b/pc/rtp_transceiver.h
@@ -59,6 +59,7 @@
 #include "pc/session_description.h"
 #include "pc/simulcast_description.h"
 #include "rtc_base/checks.h"
+#include "rtc_base/network_route.h"
 #include "rtc_base/system/plan_b_only.h"
 #include "rtc_base/thread_annotations.h"
 
@@ -166,8 +167,7 @@
       const AudioOptions& audio_options,
       const VideoOptions& video_options,
       VideoBitrateAllocatorFactory* video_bitrate_allocator_factory,
-      absl::AnyInvocable<RtpTransportInternal*(absl::string_view) &&>
-          transport_lookup,
+      absl::AnyInvocable<RtpTransportInternal*() &&> transport_lookup,
       ScopedOperationsBatcher& worker_tasks,
       ScopedOperationsBatcher& network_tasks);
 
@@ -198,8 +198,9 @@
   // ClearChannel() must be used before calling SetChannel() again.
   RTCError SetChannelForTest(
       std::unique_ptr<ChannelInterface> channel,
-      absl::AnyInvocable<RtpTransportInternal*(const std::string&) &&>
-          transport_lookup);
+      absl::AnyInvocable<RtpTransportInternal*() &&> transport_lookup = []() {
+        return nullptr;
+      });
 
   // Clear the association between the transceiver and the channel.
   void ClearChannel();
@@ -403,7 +404,8 @@
     return channel_ != nullptr;
   }
 
-  bool SetChannelRtpTransport(RtpTransportInternal* rtp_transport);
+  bool SetRtpTransport(RtpTransportInternal* rtp_transport);
+
   // Configures the channel with local content description.
   // Pushes a multi-stage execution task into the provided
   // `ScopedOperationsBatcher`. See `SetChannelContent` for details on the
@@ -445,6 +447,14 @@
                         scoped_refptr<PendingTaskSafetyFlag> safety)
       RTC_RUN_ON(context()->network_thread());
   void OnFirstPacketSent();
+  RTCErrorOr<std::optional<std::string>> InitializeOnNetworkThread(
+      absl::AnyInvocable<RtpTransportInternal*() &&> transport_lookup)
+      RTC_RUN_ON(context()->network_thread());
+  void OnNetworkRouteChanged(std::optional<NetworkRoute> network_route)
+      RTC_RUN_ON(context()->network_thread());
+  void ClearRtpTransportState() RTC_RUN_ON(context()->network_thread());
+  void SetRtpTransportState(RtpTransportInternal* transport)
+      RTC_RUN_ON(context()->network_thread());
 
   // Stops the receivers synchronously and returns a task that stops the
   // senders. The returned task must be executed on the worker thread.
@@ -529,6 +539,8 @@
   bool receptive_n_ RTC_GUARDED_BY(context()->network_thread()) = false;
   bool packet_notified_after_receptive_
       RTC_GUARDED_BY(context()->network_thread()) = false;
+  RtpTransportInternal* rtp_transport_
+      RTC_GUARDED_BY(context()->network_thread()) = nullptr;
 
   // Accessed on both thread_ and the network thread. Considered safe
   // because all access on the network thread is within an invoke()
diff --git a/pc/rtp_transceiver_unittest.cc b/pc/rtp_transceiver_unittest.cc
index 546b148..235e6f4 100644
--- a/pc/rtp_transceiver_unittest.cc
+++ b/pc/rtp_transceiver_unittest.cc
@@ -18,6 +18,7 @@
 #include <utility>
 #include <vector>
 
+#include "absl/functional/any_invocable.h"
 #include "absl/strings/string_view.h"
 #include "api/audio_options.h"
 #include "api/crypto/crypto_options.h"
@@ -33,6 +34,7 @@
 #include "api/test/rtc_error_matchers.h"
 #include "api/video_codecs/scalability_mode.h"
 #include "api/video_codecs/sdp_video_format.h"
+#include "call/call.h"
 #include "media/base/codec.h"
 #include "media/base/codec_comparators.h"
 #include "media/base/fake_media_engine.h"
@@ -58,6 +60,9 @@
 #include "pc/test/mock_channel_interface.h"
 #include "pc/test/mock_rtp_receiver_internal.h"
 #include "pc/test/mock_rtp_sender_internal.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/network_route.h"
 #include "rtc_base/thread.h"
 #include "test/create_test_environment.h"
 #include "test/gmock.h"
@@ -79,6 +84,68 @@
 
 namespace {
 
+scoped_refptr<RtpTransceiver> CreateAudioTransceiverWithChannel(
+    const Environment& env,
+    Call* call,
+    ConnectionContext* context,
+    CodecLookupHelper* codec_lookup_helper,
+    const AudioOptions& audio_options,
+    absl::AnyInvocable<RtpTransportInternal*() &&> transport_lookup = []() {
+      return nullptr;
+    }) {
+  ScopedOperationsBatcher worker_tasks(context->worker_thread());
+  auto transceiver = make_ref_counted<RtpTransceiver>(
+      env, call, MediaConfig(), "sender", "receiver", MediaType::AUDIO, nullptr,
+      std::vector<std::string>(), std::vector<RtpEncodingParameters>(), context,
+      codec_lookup_helper, nullptr, nullptr, audio_options, VideoOptions(),
+      CryptoOptions(), nullptr, std::vector<RtpHeaderExtensionCapability>(),
+      false, std::vector<SimulcastLayer>(), worker_tasks, [] {});
+
+  RTC_CHECK(worker_tasks.Run().ok());
+
+  ScopedOperationsBatcher network_tasks(context->network_thread());
+  transceiver->CreateChannel("0", call, MediaConfig(), false, CryptoOptions(),
+                             audio_options, VideoOptions(), nullptr,
+                             std::move(transport_lookup), worker_tasks,
+                             network_tasks);
+
+  RTC_CHECK(worker_tasks.Run().ok());
+  RTC_CHECK(network_tasks.Run().ok());
+
+  return transceiver;
+}
+
+scoped_refptr<RtpTransceiver> CreateVideoTransceiverWithChannel(
+    const Environment& env,
+    Call* call,
+    ConnectionContext* context,
+    CodecLookupHelper* codec_lookup_helper,
+    const VideoOptions& video_options,
+    absl::AnyInvocable<RtpTransportInternal*() &&> transport_lookup = []() {
+      return nullptr;
+    }) {
+  ScopedOperationsBatcher worker_tasks(context->worker_thread());
+  auto transceiver = make_ref_counted<RtpTransceiver>(
+      env, call, MediaConfig(), "sender", "receiver", MediaType::VIDEO, nullptr,
+      std::vector<std::string>(), std::vector<RtpEncodingParameters>(), context,
+      codec_lookup_helper, nullptr, nullptr, AudioOptions(), video_options,
+      CryptoOptions(), nullptr, std::vector<RtpHeaderExtensionCapability>(),
+      false, std::vector<SimulcastLayer>(), worker_tasks, [] {});
+
+  RTC_CHECK(worker_tasks.Run().ok());
+
+  ScopedOperationsBatcher network_tasks(context->network_thread());
+  transceiver->CreateChannel("0", call, MediaConfig(), false, CryptoOptions(),
+                             AudioOptions(), video_options, nullptr,
+                             std::move(transport_lookup), worker_tasks,
+                             network_tasks);
+
+  RTC_CHECK(worker_tasks.Run().ok());
+  RTC_CHECK(network_tasks.Run().ok());
+
+  return transceiver;
+}
+
 class RtpTransceiverTest : public testing::Test {
  public:
   RtpTransceiverTest()
@@ -121,6 +188,72 @@
   FakeCodecLookupHelper codec_lookup_helper_;
 };
 
+class RtpTransceiverDoubleThreadTest : public testing::Test {
+ public:
+  RtpTransceiverDoubleThreadTest()
+      : env_(CreateTestEnvironment()),
+        network_thread_(Thread::Create()),
+        worker_thread_(Thread::Create()),
+        dependencies_(
+            MakeDependencies(network_thread_.get(), worker_thread_.get())),
+        context_(ConnectionContext::Create(env_, &dependencies_)),
+        codec_lookup_helper_(context_.get(), env_.field_trials()) {
+    network_thread_->Start();
+    worker_thread_->Start();
+    context_->worker_thread()->BlockingCall([&]() {
+      media_engine_ref_ =
+          std::make_unique<ConnectionContext::MediaEngineReference>(context_);
+    });
+  }
+
+  ~RtpTransceiverDoubleThreadTest() override {
+    context_->worker_thread()->BlockingCall(
+        [&]() { media_engine_ref_.reset(); });
+  }
+
+ protected:
+  const Environment& env() const { return env_; }
+  FakeMediaEngine* media_engine() {
+    return static_cast<FakeMediaEngine*>(media_engine_ref_->media_engine());
+  }
+  ConnectionContext* context() { return context_.get(); }
+  CodecLookupHelper* codec_lookup_helper() { return &codec_lookup_helper_; }
+
+ private:
+  static PeerConnectionFactoryDependencies MakeDependencies(
+      Thread* network_thread,
+      Thread* worker_thread) {
+    PeerConnectionFactoryDependencies d;
+    d.network_thread = network_thread;
+    d.worker_thread = worker_thread;
+    d.signaling_thread = Thread::Current();
+    RTC_LOG(LS_INFO) << "MakeDependencies signaling_thread="
+                     << d.signaling_thread;
+    EnableFakeMedia(d, std::make_unique<FakeMediaEngine>());
+    return d;
+  }
+
+  test::RunLoop main_thread_;
+  Environment env_;
+  std::unique_ptr<Thread> network_thread_;
+  std::unique_ptr<Thread> worker_thread_;
+  PeerConnectionFactoryDependencies dependencies_;
+  scoped_refptr<ConnectionContext> context_;
+  std::unique_ptr<ConnectionContext::MediaEngineReference> media_engine_ref_;
+  FakeCodecLookupHelper codec_lookup_helper_;
+};
+
+class RtpTransceiverTestWithFakeCall : public RtpTransceiverTest {
+ public:
+  RtpTransceiverTestWithFakeCall()
+      : call_(std::make_unique<FakeCall>(env(),
+                                         Thread::Current(),
+                                         Thread::Current())) {}
+
+ protected:
+  std::unique_ptr<FakeCall> call_;
+};
+
 // Checks that a channel cannot be set on a stopped `RtpTransceiver`.
 TEST_F(RtpTransceiverTest, CannotSetChannelOnStoppedTransceiver) {
   const std::string content_name("my_mid");
@@ -132,11 +265,7 @@
   EXPECT_CALL(*channel1, mid()).WillRepeatedly(ReturnRef(content_name));
   EXPECT_CALL(*channel1, SetRtpTransport(_)).WillRepeatedly(Return(true));
 
-  transceiver->SetChannelForTest(std::move(channel1),
-                                 [&](const std::string& mid) {
-                                   EXPECT_EQ(mid, content_name);
-                                   return nullptr;
-                                 });
+  transceiver->SetChannelForTest(std::move(channel1));
   EXPECT_TRUE(transceiver->HasChannel());
 
   // Stop the transceiver.
@@ -152,8 +281,7 @@
   ASSERT_FALSE(transceiver->HasChannel());
 
   // Channel can no longer be set, so this call should be a no-op.
-  transceiver->SetChannelForTest(std::move(channel2),
-                                 [](const std::string&) { return nullptr; });
+  transceiver->SetChannelForTest(std::move(channel2));
   EXPECT_FALSE(transceiver->HasChannel());
 }
 
@@ -168,11 +296,7 @@
   EXPECT_CALL(*channel, mid()).WillRepeatedly(ReturnRef(content_name));
   EXPECT_CALL(*channel, SetRtpTransport(_)).WillRepeatedly(Return(true));
 
-  transceiver->SetChannelForTest(std::move(channel),
-                                 [&](const std::string& mid) {
-                                   EXPECT_EQ(mid, content_name);
-                                   return nullptr;
-                                 });
+  transceiver->SetChannelForTest(std::move(channel));
   EXPECT_TRUE(transceiver->HasChannel());
 
   // Stop the transceiver.
@@ -184,28 +308,17 @@
   EXPECT_FALSE(transceiver->HasChannel());
 }
 
-TEST_F(RtpTransceiverTest, TransportNameIsUpdated) {
+TEST_F(RtpTransceiverTestWithFakeCall, TransportNameIsUpdated) {
   const std::string content_name("my_mid");
-  auto transceiver = make_ref_counted<RtpTransceiver>(
-      env(), MediaType::AUDIO, context(), codec_lookup_helper(), nullptr);
-  EXPECT_FALSE(transceiver->transport_name().has_value());
 
   auto fake_dtls = std::make_unique<FakeDtlsTransport>("test_transport", false);
   auto rtp_transport = std::make_unique<RtpTransport>(/*rtcp_mux_enabled=*/true,
                                                       env().field_trials());
   rtp_transport->SetRtpPacketTransport(fake_dtls.get());
 
-  transceiver->set_mid(content_name);
-  auto channel = std::make_unique<NiceMock<MockChannelInterface>>();
-  EXPECT_CALL(*channel, media_type()).WillRepeatedly(Return(MediaType::AUDIO));
-  EXPECT_CALL(*channel, mid()).WillRepeatedly(ReturnRef(content_name));
-  EXPECT_CALL(*channel, SetRtpTransport(_)).WillRepeatedly(Return(true));
-
-  auto result = transceiver->SetChannelForTest(
-      std::move(channel), [&](const std::string& mid) -> RtpTransportInternal* {
-        return rtp_transport.get();
-      });
-  ASSERT_TRUE(result.ok());
+  auto transceiver = CreateAudioTransceiverWithChannel(
+      env(), call_.get(), context(), codec_lookup_helper(), AudioOptions(),
+      [&]() -> RtpTransportInternal* { return rtp_transport.get(); });
 
   EXPECT_TRUE(transceiver->HasChannel());
   EXPECT_EQ(transceiver->transport_name(), "test_transport");
@@ -759,8 +872,7 @@
   EXPECT_CALL(*mock_channel, mid()).WillRepeatedly(ReturnRef(content_name));
   EXPECT_CALL(*mock_channel, SetRtpTransport(_)).WillRepeatedly(Return(true));
 
-  transceiver_->SetChannelForTest(std::move(mock_channel),
-                                  [](const std::string&) { return nullptr; });
+  transceiver_->SetChannelForTest(std::move(mock_channel));
   EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(),
               ElementsAre(Field(&RtpHeaderExtensionCapability::direction,
                                 RtpTransceiverDirection::kStopped),
@@ -791,8 +903,7 @@
   description.set_rtp_header_extensions(extensions);
   transceiver_->OnNegotiationUpdate(SdpType::kAnswer, &description);
 
-  transceiver_->SetChannelForTest(std::move(mock_channel),
-                                  [](const std::string&) { return nullptr; });
+  transceiver_->SetChannelForTest(std::move(mock_channel));
 
   EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(),
               ElementsAre(Field(&RtpHeaderExtensionCapability::direction,
@@ -825,8 +936,7 @@
   description.set_rtp_header_extensions(extensions);
   transceiver_->OnNegotiationUpdate(SdpType::kPrAnswer, &description);
 
-  transceiver_->SetChannelForTest(std::move(mock_channel),
-                                  [](const std::string&) { return nullptr; });
+  transceiver_->SetChannelForTest(std::move(mock_channel));
 
   EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(),
               ElementsAre(Field(&RtpHeaderExtensionCapability::direction,
@@ -854,8 +964,7 @@
   EXPECT_CALL(*mock_channel, mid()).WillRepeatedly(ReturnRef(content_name));
   EXPECT_CALL(*mock_channel, SetRtpTransport(_)).WillRepeatedly(Return(true));
 
-  transceiver_->SetChannelForTest(std::move(mock_channel),
-                                  [](const std::string&) { return nullptr; });
+  transceiver_->SetChannelForTest(std::move(mock_channel));
 
   AudioContentDescription description_pr_answer;
   description_pr_answer.set_rtp_header_extensions({RtpExtension("uri1", 1)});
@@ -1001,48 +1110,13 @@
   EXPECT_EQ(svc_extensions[1].direction, RtpTransceiverDirection::kSendRecv);
 }
 
-class RtpTransceiverTestWithFakeCall : public RtpTransceiverTest {
- public:
-  RtpTransceiverTestWithFakeCall()
-      : call_(std::make_unique<FakeCall>(env(),
-                                         Thread::Current(),
-                                         Thread::Current())) {}
-
- protected:
-  std::unique_ptr<FakeCall> call_;
-};
-
 TEST_F(RtpTransceiverTestWithFakeCall,
        SetChannelAppliesAudioOptionsToSendChannel) {
   AudioOptions audio_options;
   audio_options.audio_network_adaptor = true;
 
-  scoped_refptr<RtpTransceiver> transceiver;
-  ScopedOperationsBatcher worker_tasks(context()->worker_thread());
-  transceiver = make_ref_counted<RtpTransceiver>(
-      env(), call_.get(), MediaConfig(),
-      /*sender_id=*/"sender", /*receiver_id=*/"receiver", MediaType::AUDIO,
-      /*track=*/nullptr,
-      /*stream_ids=*/std::vector<std::string>(),
-      /*init_send_encodings=*/std::vector<RtpEncodingParameters>(), context(),
-      codec_lookup_helper(),
-      /*legacy_stats=*/nullptr, /*set_streams_observer=*/nullptr, audio_options,
-      VideoOptions(), CryptoOptions(),
-      /*video_bitrate_allocator_factory=*/nullptr,
-      /*header_extensions=*/std::vector<RtpHeaderExtensionCapability>(),
-      /*simulcast_rejected=*/false,
-      /*initial_simulcast_layers=*/std::vector<SimulcastLayer>(), worker_tasks,
-      /*on_negotiation_needed=*/[] {});
-  EXPECT_FALSE(transceiver->HasChannel());
-  EXPECT_TRUE(worker_tasks.Run().ok());
-  ScopedOperationsBatcher network_tasks(context()->network_thread());
-  transceiver->CreateChannel(
-      "0", call_.get(), MediaConfig(), /*srtp_required=*/false, CryptoOptions(),
-      audio_options, VideoOptions(), nullptr,
-      [](absl::string_view) -> RtpTransportInternal* { return nullptr; },
-      worker_tasks, network_tasks);
-  EXPECT_TRUE(worker_tasks.Run().ok());
-  EXPECT_TRUE(network_tasks.Run().ok());
+  auto transceiver = CreateAudioTransceiverWithChannel(
+      env(), call_.get(), context(), codec_lookup_helper(), audio_options);
 
   ASSERT_TRUE(transceiver->HasChannel());
   auto* voice_channel = transceiver->voice_media_send_channel();
@@ -1050,11 +1124,174 @@
   auto* fake_channel = static_cast<FakeVoiceMediaSendChannel*>(voice_channel);
   EXPECT_TRUE(fake_channel->options().audio_network_adaptor);
 
+  ScopedOperationsBatcher worker_tasks(context()->worker_thread());
+  ScopedOperationsBatcher network_tasks(context()->network_thread());
   network_tasks.Add(transceiver->GetClearChannelNetworkTask());
   worker_tasks.Add(
       transceiver->GetDeleteChannelWorkerTask(/*stop_senders=*/true));
 }
 
+TEST_F(RtpTransceiverTestWithFakeCall, OnNetworkRouteChangedForwardsToChannel) {
+  const std::string content_name("my_mid");
+
+  auto fake_dtls = std::make_unique<FakeDtlsTransport>("test_transport", false);
+  auto rtp_transport = std::make_unique<RtpTransport>(/*rtcp_mux_enabled=*/true,
+                                                      env().field_trials());
+  rtp_transport->SetRtpPacketTransport(fake_dtls.get());
+
+  auto transceiver = CreateAudioTransceiverWithChannel(
+      env(), call_.get(), context(), codec_lookup_helper(), AudioOptions(),
+      [&]() -> RtpTransportInternal* { return rtp_transport.get(); });
+
+  ASSERT_TRUE(transceiver->HasChannel());
+  auto* voice_channel = transceiver->voice_media_send_channel();
+  ASSERT_TRUE(voice_channel);
+  auto* fake_channel = static_cast<FakeVoiceMediaSendChannel*>(voice_channel);
+
+  NetworkRoute network_route;
+  network_route.connected = true;
+  network_route.local = RouteEndpoint::CreateWithNetworkId(1);
+  network_route.remote = RouteEndpoint::CreateWithNetworkId(2);
+  network_route.last_sent_packet_id = 100;
+  network_route.packet_overhead = 28;
+
+  // Trigger network route change on the transport on the network thread.
+  context()->network_thread()->BlockingCall([&]() {
+    fake_dtls->ice_transport()->NotifyNetworkRouteChanged(
+        std::optional<NetworkRoute>(network_route));
+  });
+
+  // Verify that the channel received it.
+  EXPECT_EQ(1, fake_channel->num_network_route_changes());
+  EXPECT_TRUE(fake_channel->last_network_route().connected);
+  EXPECT_EQ(1, fake_channel->last_network_route().local.network_id());
+  EXPECT_EQ(2, fake_channel->last_network_route().remote.network_id());
+  EXPECT_EQ(100, fake_channel->last_network_route().last_sent_packet_id);
+  EXPECT_EQ(28, fake_channel->transport_overhead_per_packet());
+
+  ScopedOperationsBatcher worker_tasks(context()->worker_thread());
+  ScopedOperationsBatcher network_tasks(context()->network_thread());
+  network_tasks.Add(transceiver->GetClearChannelNetworkTask());
+  worker_tasks.Add(
+      transceiver->GetDeleteChannelWorkerTask(/*stop_senders=*/true));
+}
+
+TEST_F(RtpTransceiverTestWithFakeCall,
+       OnNetworkRouteChangedForwardsToVideoChannel) {
+  const std::string content_name("my_mid");
+
+  auto fake_dtls = std::make_unique<FakeDtlsTransport>("test_transport", false);
+  auto rtp_transport = std::make_unique<RtpTransport>(/*rtcp_mux_enabled=*/true,
+                                                      env().field_trials());
+  rtp_transport->SetRtpPacketTransport(fake_dtls.get());
+
+  auto transceiver = CreateVideoTransceiverWithChannel(
+      env(), call_.get(), context(), codec_lookup_helper(), VideoOptions(),
+      [&]() -> RtpTransportInternal* { return rtp_transport.get(); });
+
+  ASSERT_TRUE(transceiver->HasChannel());
+  auto* video_channel = transceiver->video_media_send_channel();
+  ASSERT_TRUE(video_channel);
+  auto* fake_channel = static_cast<FakeVideoMediaSendChannel*>(video_channel);
+
+  NetworkRoute network_route;
+  network_route.connected = true;
+  network_route.local = RouteEndpoint::CreateWithNetworkId(1);
+  network_route.remote = RouteEndpoint::CreateWithNetworkId(2);
+  network_route.last_sent_packet_id = 100;
+  network_route.packet_overhead = 28;
+
+  // Trigger network route change on the transport on the network thread.
+  context()->network_thread()->BlockingCall([&]() {
+    fake_dtls->ice_transport()->NotifyNetworkRouteChanged(
+        std::optional<NetworkRoute>(network_route));
+  });
+
+  // Verify that the channel received it.
+  EXPECT_EQ(1, fake_channel->num_network_route_changes());
+  EXPECT_TRUE(fake_channel->last_network_route().connected);
+  EXPECT_EQ(1, fake_channel->last_network_route().local.network_id());
+  EXPECT_EQ(2, fake_channel->last_network_route().remote.network_id());
+  EXPECT_EQ(100, fake_channel->last_network_route().last_sent_packet_id);
+  EXPECT_EQ(28, fake_channel->transport_overhead_per_packet());
+
+  ScopedOperationsBatcher worker_tasks(context()->worker_thread());
+  ScopedOperationsBatcher network_tasks(context()->network_thread());
+  network_tasks.Add(transceiver->GetClearChannelNetworkTask());
+  worker_tasks.Add(
+      transceiver->GetDeleteChannelWorkerTask(/*stop_senders=*/true));
+}
+
+TEST_F(RtpTransceiverDoubleThreadTest,
+       OnNetworkRouteChangedForwardsToChannel_DoubleThread) {
+  const std::string content_name("my_mid");
+  RTC_LOG(LS_INFO) << "TestBody current_thread=" << Thread::Current();
+  RTC_LOG(LS_INFO) << "TestBody signaling_thread="
+                   << context()->signaling_thread();
+  RTC_LOG(LS_INFO) << "TestBody network_thread=" << context()->network_thread();
+
+  std::unique_ptr<FakeDtlsTransport> fake_dtls;
+  auto rtp_transport = std::make_unique<RtpTransport>(/*rtcp_mux_enabled=*/true,
+                                                      env().field_trials());
+  context()->network_thread()->BlockingCall([&]() {
+    fake_dtls = std::make_unique<FakeDtlsTransport>(
+        "test_transport", 0, context()->network_thread());
+    rtp_transport->SetRtpPacketTransport(fake_dtls.get());
+  });
+
+  auto call = std::make_unique<FakeCall>(env(), context()->worker_thread(),
+                                         context()->network_thread());
+
+  auto transceiver = CreateAudioTransceiverWithChannel(
+      env(), call.get(), context(), codec_lookup_helper(), AudioOptions(),
+      [&]() -> RtpTransportInternal* { return rtp_transport.get(); });
+
+  ASSERT_TRUE(transceiver->HasChannel());
+  auto* voice_channel = transceiver->voice_media_send_channel();
+  ASSERT_TRUE(voice_channel);
+  auto* fake_channel = static_cast<FakeVoiceMediaSendChannel*>(voice_channel);
+
+  NetworkRoute network_route;
+  network_route.connected = true;
+  network_route.local = RouteEndpoint::CreateWithNetworkId(1);
+  network_route.remote = RouteEndpoint::CreateWithNetworkId(2);
+  network_route.last_sent_packet_id = 100;
+  network_route.packet_overhead = 28;
+
+  // Trigger network route change on the transport on the network thread.
+  context()->network_thread()->BlockingCall([&]() {
+    fake_dtls->ice_transport()->NotifyNetworkRouteChanged(
+        std::optional<NetworkRoute>(network_route));
+  });
+
+  // Verify that the channel received it.
+  int changes = context()->network_thread()->BlockingCall(
+      [&]() { return fake_channel->num_network_route_changes(); });
+  EXPECT_EQ(1, changes);
+
+  NetworkRoute last_route = context()->network_thread()->BlockingCall(
+      [&]() { return fake_channel->last_network_route(); });
+  EXPECT_TRUE(last_route.connected);
+  EXPECT_EQ(1, last_route.local.network_id());
+  EXPECT_EQ(2, last_route.remote.network_id());
+  EXPECT_EQ(100, last_route.last_sent_packet_id);
+
+  int overhead = context()->network_thread()->BlockingCall(
+      [&]() { return fake_channel->transport_overhead_per_packet(); });
+  EXPECT_EQ(28, overhead);
+
+  ScopedOperationsBatcher worker_tasks(context()->worker_thread());
+  ScopedOperationsBatcher network_tasks(context()->network_thread());
+  network_tasks.Add(transceiver->GetClearChannelNetworkTask());
+  worker_tasks.Add(
+      transceiver->GetDeleteChannelWorkerTask(/*stop_senders=*/true));
+
+  RTC_CHECK(network_tasks.Run().ok());
+  RTC_CHECK(worker_tasks.Run().ok());
+
+  context()->network_thread()->BlockingCall([&]() { fake_dtls.reset(); });
+}
+
 // Sframe tests
 
 TEST_F(RtpTransceiverUnifiedPlanTest, SframeEnabledIsNulloptByDefault) {
diff --git a/pc/sdp_offer_answer.cc b/pc/sdp_offer_answer.cc
index 09f9ca5..d576bcf 100644
--- a/pc/sdp_offer_answer.cc
+++ b/pc/sdp_offer_answer.cc
@@ -4492,7 +4492,7 @@
           content.mid(), pc_->call_ptr(), pc_->configuration()->media_config,
           pc_->SrtpRequired(), pc_->GetCryptoOptions(), audio_options(),
           video_options(), video_bitrate_allocator_factory_.get(),
-          [&](absl::string_view mid) {
+          [mid = content.mid(), this]() {
             RTC_DCHECK_RUN_ON(network_thread());
             return transport_controller_n()->GetRtpTransport(mid);
           },
@@ -5833,7 +5833,7 @@
         voice->mid(), pc_->call_ptr(), pc_->configuration()->media_config,
         pc_->SrtpRequired(), pc_->GetCryptoOptions(), audio_options(),
         video_options(), video_bitrate_allocator_factory_.get(),
-        [&](absl::string_view mid) {
+        [mid = voice->mid(), this]() {
           RTC_DCHECK_RUN_ON(network_thread());
           return transport_controller_n()->GetRtpTransport(mid);
         },
@@ -5847,7 +5847,7 @@
         video->mid(), pc_->call_ptr(), pc_->configuration()->media_config,
         pc_->SrtpRequired(), pc_->GetCryptoOptions(), audio_options(),
         video_options(), video_bitrate_allocator_factory_.get(),
-        [&](absl::string_view mid) {
+        [mid = video->mid(), this]() {
           RTC_DCHECK_RUN_ON(network_thread());
           return transport_controller_n()->GetRtpTransport(mid);
         },
diff --git a/pc/test/fake_peer_connection_for_stats.h b/pc/test/fake_peer_connection_for_stats.h
index c351e54..12c23e7 100644
--- a/pc/test/fake_peer_connection_for_stats.h
+++ b/pc/test/fake_peer_connection_for_stats.h
@@ -644,8 +644,9 @@
       std::unique_ptr<ChannelInterface> channel,
       MediaSendChannelInterface* send_channel,
       MediaReceiveChannelInterface* receive_channel) {
+    std::string mid = channel->mid();
     transceiver->SetChannelForTest(
-        std::move(channel), [this](const std::string& mid) {
+        std::move(channel), [this, mid = std::move(mid)]() {
           return transport_controller_->GetRtpTransport(mid);
         });
     RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN()