Refactor BaseChannel+ChannelInterface to return RTCError instead of bool

Update the interface and internal signaling methods in BaseChannel,
VoiceChannel, and VideoChannel to return RTCError. This replaces the
previous pattern of returning a boolean and using a separate string
reference output parameter for the error description.

Bug: none
Change-Id: I198af89fad90a77d3c3125cc4c2d31d1a0c0efcd
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/458641
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#47231}
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 8a57696..c07cbd3 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -115,6 +115,7 @@
     ":rtp_transport_internal",
     ":session_description",
     "../api:jsep",
+    "../api:rtc_error",
     "../api:rtp_parameters",
     "../media:media_channel",
     "../media:stream_params",
diff --git a/pc/channel.cc b/pc/channel.cc
index f170537..8925882 100644
--- a/pc/channel.cc
+++ b/pc/channel.cc
@@ -340,20 +340,18 @@
   }));
 }
 
-bool BaseChannel::SetLocalContent(const MediaContentDescription* content,
-                                  SdpType type,
-                                  std::string& error_desc) {
+RTCError BaseChannel::SetLocalContent(const MediaContentDescription* content,
+                                      SdpType type) {
   RTC_DCHECK_RUN_ON(worker_thread());
   TRACE_EVENT0("webrtc", "BaseChannel::SetLocalContent");
-  return SetLocalContent_w(content, type, error_desc);
+  return SetLocalContent_w(content, type);
 }
 
-bool BaseChannel::SetRemoteContent(const MediaContentDescription* content,
-                                   SdpType type,
-                                   std::string& error_desc) {
+RTCError BaseChannel::SetRemoteContent(const MediaContentDescription* content,
+                                       SdpType type) {
   RTC_DCHECK_RUN_ON(worker_thread());
   TRACE_EVENT0("webrtc", "BaseChannel::SetRemoteContent");
-  return SetRemoteContent_w(content, type, error_desc);
+  return SetRemoteContent_w(content, type);
 }
 
 bool BaseChannel::SetPayloadTypeDemuxingEnabled(bool enabled) {
@@ -575,18 +573,18 @@
   media_receive_channel()->OnPacketReceived(parsed_packet);
 }
 
-bool BaseChannel::MaybeUpdateDemuxerAndRtpExtensions_w(
+RTCError BaseChannel::MaybeUpdateDemuxerAndRtpExtensions_w(
     bool update_demuxer,
     std::optional<flat_set<uint8_t>> payload_types,
     const RtpHeaderExtensions& extensions,
-    std::optional<flat_set<uint32_t>> ssrcs,
-    std::string& error_desc) {
+    std::optional<flat_set<uint32_t>> ssrcs) {
   bool update_extensions = true;
   if (rtp_header_extensions_ == extensions) {
     update_extensions = false;  // No need to update header extensions.
   } else {
-    if (!CheckRtpExtensionValidity(extensions, error_desc)) {
-      return false;
+    RTCError error = CheckRtpExtensionValidity(extensions);
+    if (!error.ok()) {
+      return error;
     }
     rtp_header_extensions_ = extensions;
 
@@ -604,7 +602,7 @@
 
   if (!update_demuxer && !update_extensions && !payload_types.has_value() &&
       !ssrcs.has_value()) {
-    return true;  // No update needed.
+    return RTCError::OK();  // No update needed.
   }
 
   // TODO(bugs.webrtc.org/13536): See if we can do this asynchronously.
@@ -613,13 +611,13 @@
     media_receive_channel()->OnDemuxerCriteriaUpdatePending();
   }
 
-  bool success = network_thread()->BlockingCall([&] {
+  RTCError error = network_thread()->BlockingCall([&]() -> RTCError {
     RTC_DCHECK_RUN_ON(network_thread());
     if (!rtp_transport_) {
       // To repro this situation, run the
       // `ApplyDescriptionWithSameSsrcsBundledFails` test.
-      RTC_LOG(LS_ERROR) << "No transport assigned for mid=" << mid();
-      return false;
+      return LOG_ERROR(RTCError::InvalidState()
+                       << "No transport assigned for mid=" << mid());
     }
 
     if (update_extensions) {
@@ -641,22 +639,21 @@
     }
 
     if (!update_demuxer)
-      return true;
+      return RTCError::OK();
 
     RtpDemuxerCriteria criteria = demuxer_criteria();
     if (!rtp_transport_->RegisterRtpDemuxerSink(criteria, this)) {
-      error_desc =
-          StringFormat("Failed to apply demuxer criteria for '%s': '%s'.",
-                       mid().c_str(), criteria.ToString().c_str());
-      return false;
+      return RTCError::InvalidParameter()
+             << "Failed to apply demuxer criteria for '" << mid() << "': '"
+             << criteria.ToString() << "'.";
     }
-    return true;
+    return RTCError::OK();
   });
 
   if (update_demuxer || payload_types.has_value() || ssrcs.has_value())
     media_receive_channel()->OnDemuxerCriteriaUpdateComplete();
 
-  return success;
+  return error;
 }
 
 bool BaseChannel::RegisterRtpDemuxerSink_w(
@@ -769,10 +766,9 @@
   RTC_LOG(LS_INFO) << "Channel not writable (" << ToString() << ")";
 }
 
-
-bool BaseChannel::UpdateLocalStreams_w(const std::vector<StreamParams>& streams,
-                                       SdpType type,
-                                       std::string& error_desc) {
+RTCError BaseChannel::UpdateLocalStreams_w(
+    const std::vector<StreamParams>& streams,
+    SdpType type) {
   // In the case of RIDs (where SSRCs are not negotiated), this method will
   // generate an SSRC for each layer in StreamParams. That representation will
   // be stored internally in `local_streams_`.
@@ -785,18 +781,16 @@
   // in which niether SSRCs or RIDs are negotiated.
 
   // Check for streams that have been removed.
-  bool ret = true;
   for (const StreamParams& old_stream : local_streams_) {
     if (!old_stream.has_ssrcs() ||
         GetStream(streams, StreamFinder(&old_stream))) {
       continue;
     }
     if (!media_send_channel()->RemoveSendStream(old_stream.first_ssrc())) {
-      error_desc = StringFormat(
-          "Failed to remove send stream with ssrc %u from m-section with "
-          "mid='%s'.",
-          old_stream.first_ssrc(), mid().c_str());
-      ret = false;
+      return RTCError::InvalidParameter()
+             << "Failed to remove send stream with ssrc "
+             << old_stream.first_ssrc() << " from m-section with mid='" << mid()
+             << "'.";
     }
   }
   // Check for new streams.
@@ -818,12 +812,10 @@
 
     RTC_DCHECK(new_stream.has_ssrcs() || new_stream.has_rids());
     if (new_stream.has_ssrcs() && new_stream.has_rids()) {
-      error_desc = StringFormat(
-          "Failed to add send stream: %u into m-section with mid='%s'. Stream "
-          "has both SSRCs and RIDs.",
-          new_stream.first_ssrc(), mid().c_str());
-      ret = false;
-      continue;
+      return RTCError::InvalidParameter()
+             << "Failed to add send stream: " << new_stream.first_ssrc()
+             << " into m-section with mid='" << mid()
+             << "'. Stream has both SSRCs and RIDs.";
     }
 
     // At this point we use the legacy simulcast group in StreamParams to
@@ -838,19 +830,18 @@
       RTC_LOG(LS_INFO) << "Add send stream ssrc: " << new_stream.ssrcs[0]
                        << " into " << ToString();
     } else {
-      error_desc = StringFormat(
-          "Failed to add send stream ssrc: %u into m-section with mid='%s'",
-          new_stream.first_ssrc(), mid().c_str());
-      ret = false;
+      return RTCError::InvalidParameter()
+             << "Failed to add send stream ssrc: " << new_stream.first_ssrc()
+             << " into m-section with mid='" << mid() << "'";
     }
   }
   local_streams_ = all_streams;
-  return ret;
+  return RTCError::OK();
 }
 
-bool BaseChannel::UpdateRemoteStreams_w(const MediaContentDescription* content,
-                                        SdpType type,
-                                        std::string& error_desc) {
+RTCError BaseChannel::UpdateRemoteStreams_w(
+    const MediaContentDescription* content,
+    SdpType type) {
   RTC_LOG_THREAD_BLOCK_COUNT();
   bool clear_payload_types = false;
   if (!RtpTransceiverDirectionHasSend(content->direction())) {
@@ -878,11 +869,10 @@
         RTC_LOG(LS_INFO) << "Remove remote ssrc: " << old_stream.first_ssrc()
                          << " from " << ToString() << ".";
       } else {
-        error_desc = StringFormat(
-            "Failed to remove remote stream with ssrc %u from m-section with "
-            "mid='%s'.",
-            old_stream.first_ssrc(), mid().c_str());
-        return false;
+        return RTCError::InvalidParameter()
+               << "Failed to remove remote stream with ssrc "
+               << old_stream.first_ssrc() << " from m-section with mid='"
+               << mid() << "'.";
       }
     }
   }
@@ -902,13 +892,12 @@
                                  : "unsignaled")
                          << " to " << ToString();
       } else {
-        error_desc =
-            StringFormat("Failed to add remote stream ssrc: %s to %s",
-                         new_stream.has_ssrcs()
-                             ? std::to_string(new_stream.first_ssrc()).c_str()
-                             : "unsignaled",
-                         ToString().c_str());
-        return false;
+        return RTCError::InvalidParameter()
+               << "Failed to add remote stream ssrc: "
+               << (new_stream.has_ssrcs()
+                       ? std::to_string(new_stream.first_ssrc())
+                       : "unsignaled")
+               << " to " << ToString();
       }
     }
     // Update the receiving SSRCs.
@@ -919,9 +908,8 @@
 
   // Re-register the sink to update after changing the demuxer criteria.
   if (!RegisterRtpDemuxerSink_w(clear_payload_types, std::move(ssrcs))) {
-    error_desc = StringFormat("Failed to set up audio demuxing for mid='%s'.",
-                              mid().c_str());
-    return false;
+    return RTCError::InvalidParameter()
+           << "Failed to set up audio demuxing for mid='" << mid() << "'.";
   }
 
   remote_streams_ = streams;
@@ -931,7 +919,7 @@
 
   RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(1);
 
-  return true;
+  return RTCError::OK();
 }
 
 RtpHeaderExtensions BaseChannel::GetDeduplicatedRtpHeaderExtensions(
@@ -940,23 +928,20 @@
                                                    extensions_filter_);
 }
 
-bool BaseChannel::CheckRtpExtensionValidity(
-    const RtpHeaderExtensions& extensions,
-    std::string& error_desc) const {
+RTCError BaseChannel::CheckRtpExtensionValidity(
+    const RtpHeaderExtensions& extensions) const {
   std::bitset<1 + RtpExtension::kMaxId> id_used;
   for (const auto& extension : extensions) {
     if (extension.id == 0)
       continue;
     if (extension.id < RtpExtension::kMinId ||
         extension.id > RtpExtension::kMaxId) {
-      error_desc = StringFormat("Bad RTP extension ID: %s",
-                                extension.ToString().c_str());
-      return false;
+      return RTCError::InvalidParameter()
+             << "Bad RTP extension ID: " << extension.ToString();
     }
     if (id_used[extension.id]) {
-      error_desc = StringFormat("Duplicate RTP extension ID: %s",
-                                extension.ToString().c_str());
-      return false;
+      return RTCError::InvalidParameter()
+             << "Duplicate RTP extension ID: " << extension.ToString();
     }
     id_used[extension.id] = true;
   }
@@ -969,17 +954,15 @@
         [&](const RtpExtension& ext) { return ext.id == new_extension.id; });
     if (it != historical_rtp_header_extensions_.end() &&
         it->uri != new_extension.uri) {
-      error_desc = StringFormat(
-          "Failed to update RTP header extensions for m-section with "
-          "mid='%s'. RTP extension ID reassignment from %s to %s for ID "
-          "%d.",
-          mid().c_str(), it->uri.c_str(), new_extension.uri.c_str(),
-          new_extension.id);
-      return false;
+      return RTCError::InvalidParameter()
+             << "Failed to update RTP header extensions for m-section with "
+             << "mid='" << mid() << "'. RTP extension ID reassignment from "
+             << it->uri << " to " << new_extension.uri << " for ID "
+             << new_extension.id << ".";
     }
   }
 
-  return true;
+  return RTCError::OK();
 }
 
 void BaseChannel::SignalSentPacket_n(const SentPacketInfo& sent_packet) {
@@ -1030,9 +1013,8 @@
                    << " send=" << send << " for " << ToString();
 }
 
-bool VoiceChannel::SetLocalContent_w(const MediaContentDescription* content,
-                                     SdpType type,
-                                     std::string& error_desc) {
+RTCError VoiceChannel::SetLocalContent_w(const MediaContentDescription* content,
+                                         SdpType type) {
   TRACE_EVENT0("webrtc", "VoiceChannel::SetLocalContent_w");
   RTC_DLOG(LS_INFO) << "Setting local voice description for " << ToString();
 
@@ -1041,8 +1023,9 @@
   RtpHeaderExtensions header_extensions =
       GetDeduplicatedRtpHeaderExtensions(content->rtp_header_extensions());
 
-  if (!CheckRtpExtensionValidity(header_extensions, error_desc)) {
-    return false;
+  RTCError error = CheckRtpExtensionValidity(header_extensions);
+  if (!error.ok()) {
+    return error;
   }
 
   // TODO: issues.webrtc.org/383078466 - remove if pushdown on answer is enough.
@@ -1055,11 +1038,10 @@
   recv_params.mid = mid();
 
   if (!media_receive_channel()->SetReceiverParameters(recv_params)) {
-    error_desc = StringFormat(
-        "Failed to set local audio description recv parameters for m-section "
-        "with mid='%s'.",
-        mid().c_str());
-    return false;
+    return RTCError::InvalidParameter()
+           << "Failed to set local audio description recv parameters for "
+              "m-section with mid='"
+           << mid() << "'.";
   }
 
   std::optional<flat_set<uint8_t>> payload_types;
@@ -1078,17 +1060,16 @@
     send_params.extensions = header_extensions;
     send_params.extmap_allow_mixed = content->extmap_allow_mixed();
     if (!media_send_channel()->SetSenderParameters(send_params)) {
-      error_desc = StringFormat(
-          "Failed to set send parameters for m-section with mid='%s'.",
-          mid().c_str());
-      return false;
+      return RTCError::InvalidParameter()
+             << "Failed to set send parameters for m-section with mid='"
+             << mid() << "'.";
     }
     last_send_params_ = send_params;
   }
 
-  if (!UpdateLocalStreams_w(content->streams(), type, error_desc)) {
-    RTC_DCHECK(!error_desc.empty());
-    return false;
+  error = UpdateLocalStreams_w(content->streams(), type);
+  if (!error.ok()) {
+    return error;
   }
 
   set_local_content_direction(content->direction());
@@ -1098,19 +1079,15 @@
   // TODO: https://issues.webrtc.org/360058654 - reenable after cleanup
   // RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(0);
 
-  bool success = MaybeUpdateDemuxerAndRtpExtensions_w(
+  return MaybeUpdateDemuxerAndRtpExtensions_w(
       /*update_demuxer=*/false, std::move(payload_types),
       recv_params.extensions,
-      /*ssrcs=*/std::nullopt, error_desc);
-
-  // RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(1);
-
-  return success;
+      /*ssrcs=*/std::nullopt);
 }
 
-bool VoiceChannel::SetRemoteContent_w(const MediaContentDescription* content,
-                                      SdpType type,
-                                      std::string& error_desc) {
+RTCError VoiceChannel::SetRemoteContent_w(
+    const MediaContentDescription* content,
+    SdpType type) {
   TRACE_EVENT0("webrtc", "VoiceChannel::SetRemoteContent_w");
   RTC_LOG(LS_INFO) << "Setting remote voice description for " << ToString();
 
@@ -1119,28 +1096,25 @@
                                         &send_params);
   send_params.mid = mid();
 
-  if (!CheckRtpExtensionValidity(send_params.extensions, error_desc)) {
-    return false;
+  RTCError error = CheckRtpExtensionValidity(send_params.extensions);
+  if (!error.ok()) {
+    return error;
   }
 
-  bool parameters_applied =
-      media_send_channel()->SetSenderParameters(send_params);
-  if (!parameters_applied) {
-    error_desc = StringFormat(
-        "Failed to set remote audio description send parameters for m-section "
-        "with mid='%s'.",
-        mid().c_str());
-    return false;
+  if (!media_send_channel()->SetSenderParameters(send_params)) {
+    return RTCError::InvalidParameter()
+           << "Failed to set remote audio description send parameters for "
+              "m-section with mid='"
+           << mid() << "'.";
   }
 
   if (type == SdpType::kAnswer || type == SdpType::kPrAnswer) {
     AudioReceiverParameters recv_params = last_recv_params_;
     recv_params.extensions = send_params.extensions;
     if (!media_receive_channel()->SetReceiverParameters(recv_params)) {
-      error_desc = StringFormat(
-          "Failed to set recv parameters for m-section with mid='%s'.",
-          mid().c_str());
-      return false;
+      return RTCError::InvalidParameter()
+             << "Failed to set recv parameters for m-section with mid='"
+             << mid() << "'.";
     }
     last_recv_params_ = recv_params;
   }
@@ -1158,18 +1132,14 @@
       media_send_channel()->SenderNonSenderRttEnabled());
   last_send_params_ = send_params;
 
-  if (!UpdateRemoteStreams_w(content, type, error_desc)) {
-    return false;
-  }
-
-  bool success = true;
-  if (type == SdpType::kAnswer || type == SdpType::kPrAnswer) {
-    success = MaybeUpdateDemuxerAndRtpExtensions_w(
+  error = UpdateRemoteStreams_w(content, type);
+  if (error.ok() && (type == SdpType::kAnswer || type == SdpType::kPrAnswer)) {
+    error = MaybeUpdateDemuxerAndRtpExtensions_w(
         /*update_demuxer=*/false, /*payload_types=*/std::nullopt,
-        last_recv_params_.extensions, /*ssrcs=*/std::nullopt, error_desc);
+        last_recv_params_.extensions, /*ssrcs=*/std::nullopt);
   }
 
-  return success;
+  return error;
 }
 
 VideoChannel::VideoChannel(
@@ -1212,9 +1182,8 @@
                    << " send=" << send << " for " << ToString();
 }
 
-bool VideoChannel::SetLocalContent_w(const MediaContentDescription* content,
-                                     SdpType type,
-                                     std::string& error_desc) {
+RTCError VideoChannel::SetLocalContent_w(const MediaContentDescription* content,
+                                         SdpType type) {
   TRACE_EVENT0("webrtc", "VideoChannel::SetLocalContent_w");
 
   RTC_LOG_THREAD_BLOCK_COUNT();
@@ -1222,8 +1191,9 @@
   RtpHeaderExtensions header_extensions =
       GetDeduplicatedRtpHeaderExtensions(content->rtp_header_extensions());
 
-  if (!CheckRtpExtensionValidity(header_extensions, error_desc)) {
-    return false;
+  RTCError error = CheckRtpExtensionValidity(header_extensions);
+  if (!error.ok()) {
+    return error;
   }
 
   // TODO: issues.webrtc.org/383078466 - remove if pushdown on answer is enough.
@@ -1244,19 +1214,17 @@
   // offer by ignoring the packetiztion and fall back to standard packetization
   // instead.
   if (type == SdpType::kAnswer || type == SdpType::kPrAnswer) {
-    RTCError status = MaybeIgnorePacketization(recv_params, send_params);
-    if (!status.ok()) {
-      error_desc = status.message();
-      return false;
+    error = MaybeIgnorePacketization(recv_params, send_params);
+    if (!error.ok()) {
+      return error;
     }
   }
 
   if (!media_receive_channel()->SetReceiverParameters(recv_params)) {
-    error_desc = StringFormat(
-        "Failed to set local video description recv parameters for m-section "
-        "with mid='%s'.",
-        mid().c_str());
-    return false;
+    return RTCError::InvalidParameter()
+           << "Failed to set local video description recv parameters for "
+              "m-section with mid='"
+           << mid() << "'.";
   }
 
   std::optional<flat_set<uint8_t>> payload_types;
@@ -1271,17 +1239,16 @@
 
   if (type == SdpType::kAnswer || type == SdpType::kPrAnswer) {
     if (!media_send_channel()->SetSenderParameters(send_params)) {
-      error_desc = StringFormat(
-          "Failed to set send parameters for m-section with mid='%s'.",
-          mid().c_str());
-      return false;
+      return RTCError::InvalidParameter()
+             << "Failed to set send parameters for m-section with mid='"
+             << mid() << "'.";
     }
     last_send_params_ = send_params;
   }
 
-  if (!UpdateLocalStreams_w(content->streams(), type, error_desc)) {
-    RTC_DCHECK(!error_desc.empty());
-    return false;
+  error = UpdateLocalStreams_w(content->streams(), type);
+  if (!error.ok()) {
+    return error;
   }
 
   set_local_content_direction(content->direction());
@@ -1289,18 +1256,14 @@
 
   RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(0);
 
-  bool success = MaybeUpdateDemuxerAndRtpExtensions_w(
+  return MaybeUpdateDemuxerAndRtpExtensions_w(
       /*update_demuxer=*/false, std::move(payload_types),
-      recv_params.extensions, /*ssrcs=*/std::nullopt, error_desc);
-
-  RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(1);
-
-  return success;
+      recv_params.extensions, /*ssrcs=*/std::nullopt);
 }
 
-bool VideoChannel::SetRemoteContent_w(const MediaContentDescription* content,
-                                      SdpType type,
-                                      std::string& error_desc) {
+RTCError VideoChannel::SetRemoteContent_w(
+    const MediaContentDescription* content,
+    SdpType type) {
   TRACE_EVENT0("webrtc", "VideoChannel::SetRemoteContent_w");
   RTC_LOG(LS_INFO) << "Setting remote video description for " << ToString();
 
@@ -1310,8 +1273,9 @@
   send_params.mid = mid();
   send_params.conference_mode = content->conference_mode();
 
-  if (!CheckRtpExtensionValidity(send_params.extensions, error_desc)) {
-    return false;
+  RTCError error = CheckRtpExtensionValidity(send_params.extensions);
+  if (!error.ok()) {
+    return error;
   }
 
   VideoReceiverParameters recv_params = last_recv_params_;
@@ -1322,47 +1286,38 @@
   // offer by ignoring the packetiztion and fall back to standard packetization
   // instead.
   if (type == SdpType::kAnswer || type == SdpType::kPrAnswer) {
-    RTCError status = MaybeIgnorePacketization(send_params, recv_params);
-    if (!status.ok()) {
-      error_desc = status.message();
-      return false;
+    error = MaybeIgnorePacketization(send_params, recv_params);
+    if (!error.ok()) {
+      return error;
     }
   }
 
   if (!media_send_channel()->SetSenderParameters(send_params)) {
-    error_desc = StringFormat(
-        "Failed to set remote video description send parameters for m-section "
-        "with mid='%s'.",
-        mid().c_str());
-    return false;
+    return RTCError::InvalidParameter()
+           << "Failed to set remote video description send parameters for "
+              "m-section with mid='"
+           << mid() << "'.";
   }
   if (type == SdpType::kAnswer || type == SdpType::kPrAnswer) {
     recv_params.extensions = send_params.extensions;
     recv_params.rtcp.reduced_size = send_params.rtcp.reduced_size;
     if (!media_receive_channel()->SetReceiverParameters(recv_params)) {
-      error_desc = StringFormat(
-          "Failed to set recv parameters for m-section with mid='%s'.",
-          mid().c_str());
-      return false;
+      return RTCError::InvalidParameter()
+             << "Failed to set recv parameters for m-section with mid='"
+             << mid() << "'.";
     }
     last_recv_params_ = recv_params;
   }
   last_send_params_ = send_params;
 
-  if (!UpdateRemoteStreams_w(content, type, error_desc)) {
-    return false;
-  }
-
-  if (type == SdpType::kAnswer || type == SdpType::kPrAnswer) {
-    bool success = MaybeUpdateDemuxerAndRtpExtensions_w(
+  error = UpdateRemoteStreams_w(content, type);
+  if (error.ok() && (type == SdpType::kAnswer || type == SdpType::kPrAnswer)) {
+    error = MaybeUpdateDemuxerAndRtpExtensions_w(
         /*update_demuxer=*/false, /*payload_types=*/std::nullopt,
-        last_recv_params_.extensions, /*ssrcs=*/std::nullopt, error_desc);
-    if (!success) {
-      return false;
-    }
+        last_recv_params_.extensions, /*ssrcs=*/std::nullopt);
   }
 
-  return true;
+  return error;
 }
 
 }  // namespace webrtc
diff --git a/pc/channel.h b/pc/channel.h
index f4488cd..6b4438a 100644
--- a/pc/channel.h
+++ b/pc/channel.h
@@ -24,6 +24,7 @@
 #include "api/crypto/crypto_options.h"
 #include "api/jsep.h"
 #include "api/media_types.h"
+#include "api/rtc_error.h"
 #include "api/rtp_parameters.h"
 #include "api/rtp_transceiver_direction.h"
 #include "api/scoped_refptr.h"
@@ -122,12 +123,10 @@
   }
 
   // Channel control
-  bool SetLocalContent(const MediaContentDescription* content,
-                       SdpType type,
-                       std::string& error_desc) override;
-  bool SetRemoteContent(const MediaContentDescription* content,
-                        SdpType type,
-                        std::string& error_desc) override;
+  RTCError SetLocalContent(const MediaContentDescription* content,
+                           SdpType type) override;
+  RTCError SetRemoteContent(const MediaContentDescription* content,
+                            SdpType type) override;
   // Controls whether this channel will receive packets on the basis of
   // matching payload type alone. This is needed for legacy endpoints that
   // don't signal SSRCs or use MID/RID, but doesn't make sense if there is
@@ -255,21 +254,15 @@
   // Updates the send/recv state of the media channel.
   virtual void UpdateMediaSendRecvState_w() RTC_RUN_ON(worker_thread()) = 0;
 
-  bool UpdateLocalStreams_w(const std::vector<StreamParams>& streams,
-                            SdpType type,
-                            std::string& error_desc)
-      RTC_RUN_ON(worker_thread());
-  bool UpdateRemoteStreams_w(const MediaContentDescription* content,
-                             SdpType type,
-                             std::string& error_desc)
-      RTC_RUN_ON(worker_thread());
-  virtual bool SetLocalContent_w(const MediaContentDescription* content,
-                                 SdpType type,
-                                 std::string& error_desc)
+  RTCError UpdateLocalStreams_w(const std::vector<StreamParams>& streams,
+                                SdpType type) RTC_RUN_ON(worker_thread());
+  RTCError UpdateRemoteStreams_w(const MediaContentDescription* content,
+                                 SdpType type) RTC_RUN_ON(worker_thread());
+  virtual RTCError SetLocalContent_w(const MediaContentDescription* content,
+                                     SdpType type)
       RTC_RUN_ON(worker_thread()) = 0;
-  virtual bool SetRemoteContent_w(const MediaContentDescription* content,
-                                  SdpType type,
-                                  std::string& error_desc)
+  virtual RTCError SetRemoteContent_w(const MediaContentDescription* content,
+                                      SdpType type)
       RTC_RUN_ON(worker_thread()) = 0;
 
   // Returns a list of RTP header extensions where any extension URI is unique.
@@ -282,19 +275,17 @@
   // This verifies that all extension IDs are within the valid range,
   // that there are no duplicate IDs, and that no existing extension ID
   // has been reassigned to a different URI.
-  bool CheckRtpExtensionValidity(const RtpHeaderExtensions& extensions,
-                                 std::string& error_desc) const
-      RTC_RUN_ON(worker_thread());
+  RTCError CheckRtpExtensionValidity(
+      const RtpHeaderExtensions& extensions) const RTC_RUN_ON(worker_thread());
 
   // Returns `true` if either an update wasn't needed or one was successfully
   // applied. If the return value is `false`, then updating the demuxer criteria
   // failed, which needs to be treated as an error.
-  bool MaybeUpdateDemuxerAndRtpExtensions_w(
+  RTCError MaybeUpdateDemuxerAndRtpExtensions_w(
       bool update_demuxer,
       std::optional<flat_set<uint8_t>> payload_types,
       const RtpHeaderExtensions& extensions,
-      std::optional<flat_set<uint32_t>> ssrcs,
-      std::string& error_desc) RTC_RUN_ON(worker_thread());
+      std::optional<flat_set<uint32_t>> ssrcs) RTC_RUN_ON(worker_thread());
 
   // Registers a demuxer criteria with the transport, on the network thread.
   // This function will fail if there's no transport of if a sink is already
@@ -432,13 +423,10 @@
  private:
   // overrides from BaseChannel
   void UpdateMediaSendRecvState_w() RTC_RUN_ON(worker_thread()) override;
-  bool SetLocalContent_w(const MediaContentDescription* content,
-                         SdpType type,
-                         std::string& error_desc)
-      RTC_RUN_ON(worker_thread()) override;
-  bool SetRemoteContent_w(const MediaContentDescription* content,
-                          SdpType type,
-                          std::string& error_desc)
+  RTCError SetLocalContent_w(const MediaContentDescription* content,
+                             SdpType type) RTC_RUN_ON(worker_thread()) override;
+  RTCError SetRemoteContent_w(const MediaContentDescription* content,
+                              SdpType type)
       RTC_RUN_ON(worker_thread()) override;
 
   // Last AudioSenderParameter sent down to the media_channel() via
@@ -499,13 +487,10 @@
  private:
   // overrides from BaseChannel
   void UpdateMediaSendRecvState_w() RTC_RUN_ON(worker_thread()) override;
-  bool SetLocalContent_w(const MediaContentDescription* content,
-                         SdpType type,
-                         std::string& error_desc)
-      RTC_RUN_ON(worker_thread()) override;
-  bool SetRemoteContent_w(const MediaContentDescription* content,
-                          SdpType type,
-                          std::string& error_desc)
+  RTCError SetLocalContent_w(const MediaContentDescription* content,
+                             SdpType type) RTC_RUN_ON(worker_thread()) override;
+  RTCError SetRemoteContent_w(const MediaContentDescription* content,
+                              SdpType type)
       RTC_RUN_ON(worker_thread()) override;
 
   // Last VideoSenderParameters sent down to the media_channel() via
diff --git a/pc/channel_interface.h b/pc/channel_interface.h
index 1e1c9bd..97fed01 100644
--- a/pc/channel_interface.h
+++ b/pc/channel_interface.h
@@ -18,6 +18,7 @@
 #include "absl/strings/string_view.h"
 #include "api/jsep.h"
 #include "api/media_types.h"
+#include "api/rtc_error.h"
 #include "media/base/media_channel.h"
 #include "media/base/stream_params.h"
 #include "pc/rtp_transport_internal.h"
@@ -86,12 +87,10 @@
       absl::AnyInvocable<void(const RtpPacketReceived&)> callback) = 0;
 
   // Channel control
-  virtual bool SetLocalContent(const MediaContentDescription* content,
-                               SdpType type,
-                               std::string& error_desc) = 0;
-  virtual bool SetRemoteContent(const MediaContentDescription* content,
-                                SdpType type,
-                                std::string& error_desc) = 0;
+  virtual RTCError SetLocalContent(const MediaContentDescription* content,
+                                   SdpType type) = 0;
+  virtual RTCError SetRemoteContent(const MediaContentDescription* content,
+                                    SdpType type) = 0;
   virtual bool SetPayloadTypeDemuxingEnabled(bool enabled) = 0;
 
   // Access to the local and remote streams that were set on the channel.
diff --git a/pc/channel_unittest.cc b/pc/channel_unittest.cc
index 3f58833..aae219f 100644
--- a/pc/channel_unittest.cc
+++ b/pc/channel_unittest.cc
@@ -24,6 +24,7 @@
 #include "api/crypto/crypto_options.h"
 #include "api/field_trials.h"
 #include "api/jsep.h"
+#include "api/rtc_error.h"
 #include "api/rtp_headers.h"
 #include "api/rtp_parameters.h"
 #include "api/rtp_transceiver_direction.h"
@@ -75,6 +76,7 @@
 using ::webrtc::FieldTrials;
 using ::webrtc::RidDescription;
 using ::webrtc::RidDirection;
+using ::webrtc::RTCError;
 using ::webrtc::RtpExtension;
 using ::webrtc::RtpTransceiverDirection;
 using ::webrtc::SdpType;
@@ -197,6 +199,7 @@
                        typename T::Options(), network_thread_),
                    flags1, flags2);
   }
+
   void CreateChannels(std::unique_ptr<typename T::MediaSendChannel> ch1s,
                       std::unique_ptr<typename T::MediaReceiveChannel> ch1r,
                       std::unique_ptr<typename T::MediaSendChannel> ch2s,
@@ -305,6 +308,7 @@
       AddLegacyStreamInContent(kSsrc2, flags2, &remote_media_content2_);
     }
   }
+
   std::unique_ptr<typename T::Channel> CreateChannel(
       webrtc::Thread* worker_thread,
       webrtc::Thread* network_thread,
@@ -411,65 +415,61 @@
   }
 
   bool SendInitiate() {
-    std::string err;
-    bool result = channel1_->SetLocalContent(&local_media_content1_,
-                                             SdpType::kOffer, err);
-    if (result) {
+    RTCError result =
+        channel1_->SetLocalContent(&local_media_content1_, SdpType::kOffer);
+    if (result.ok()) {
       channel1_->Enable(true);
       FlushCurrentThread();
-      result = channel2_->SetRemoteContent(&remote_media_content1_,
-                                           SdpType::kOffer, err);
-      if (result) {
+      result =
+          channel2_->SetRemoteContent(&remote_media_content1_, SdpType::kOffer);
+      if (result.ok()) {
         ConnectFakeTransports();
         result = channel2_->SetLocalContent(&local_media_content2_,
-                                            SdpType::kAnswer, err);
+                                            SdpType::kAnswer);
       }
     }
-    return result;
+    return result.ok();
   }
 
   bool SendAccept() {
     channel2_->Enable(true);
     FlushCurrentThread();
-    std::string err;
-    return channel1_->SetRemoteContent(&remote_media_content2_,
-                                       SdpType::kAnswer, err);
+    return channel1_
+        ->SetRemoteContent(&remote_media_content2_, SdpType::kAnswer)
+        .ok();
   }
 
   bool SendOffer() {
-    std::string err;
-    bool result = channel1_->SetLocalContent(&local_media_content1_,
-                                             SdpType::kOffer, err);
-    if (result) {
+    RTCError result =
+        channel1_->SetLocalContent(&local_media_content1_, SdpType::kOffer);
+    if (result.ok()) {
       channel1_->Enable(true);
-      result = channel2_->SetRemoteContent(&remote_media_content1_,
-                                           SdpType::kOffer, err);
+      result =
+          channel2_->SetRemoteContent(&remote_media_content1_, SdpType::kOffer);
     }
-    return result;
+    return result.ok();
   }
 
   bool SendProvisionalAnswer() {
-    std::string err;
-    bool result = channel2_->SetLocalContent(&local_media_content2_,
-                                             SdpType::kPrAnswer, err);
-    if (result) {
+    RTCError result =
+        channel2_->SetLocalContent(&local_media_content2_, SdpType::kPrAnswer);
+    if (result.ok()) {
       channel2_->Enable(true);
       result = channel1_->SetRemoteContent(&remote_media_content2_,
-                                           SdpType::kPrAnswer, err);
+                                           SdpType::kPrAnswer);
       ConnectFakeTransports();
     }
-    return result;
+    return result.ok();
   }
 
   bool SendFinalAnswer() {
-    std::string err;
-    bool result = channel2_->SetLocalContent(&local_media_content2_,
-                                             SdpType::kAnswer, err);
-    if (result) {
+    RTCError result =
+        channel2_->SetLocalContent(&local_media_content2_, SdpType::kAnswer);
+    if (result.ok()) {
       result = channel1_->SetRemoteContent(&remote_media_content2_,
-                                           SdpType::kAnswer, err);
+                                           SdpType::kAnswer);
     }
-    return result;
+    return result.ok();
   }
 
   void SendRtp(typename T::MediaSendChannel* media_channel,
@@ -636,10 +636,9 @@
     CreateChannels(0, 0);
     typename T::Content content;
     CreateContent(0, kPcmuCodec, kH264Codec, &content);
-    std::string err;
-    EXPECT_TRUE(channel1_->SetLocalContent(&content, SdpType::kOffer, err));
+    EXPECT_TRUE(channel1_->SetLocalContent(&content, SdpType::kOffer).ok());
     EXPECT_EQ(0U, media_send_channel1_impl()->send_codecs().size());
-    EXPECT_TRUE(channel1_->SetRemoteContent(&content, SdpType::kAnswer, err));
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content, SdpType::kAnswer).ok());
     ASSERT_EQ(1U, media_send_channel1_impl()->send_codecs().size());
     EXPECT_EQ(content.codecs()[0],
               media_send_channel1_impl()->send_codecs()[0]);
@@ -659,11 +658,8 @@
     });
 
     CreateChannels(0, 0);
-    std::string err;
-    ASSERT_TRUE(channel1_->SetLocalContent(&local, SdpType::kOffer, err))
-        << err;
-    ASSERT_TRUE(channel1_->SetRemoteContent(&remote, SdpType::kAnswer, err))
-        << err;
+    ASSERT_TRUE(channel1_->SetLocalContent(&local, SdpType::kOffer).ok());
+    ASSERT_TRUE(channel1_->SetRemoteContent(&remote, SdpType::kAnswer).ok());
 
     EXPECT_THAT(media_receive_channel1_impl()->recv_extensions(),
                 ElementsAre(AllOf(Field("id", &RtpExtension::id, 1),
@@ -688,11 +684,8 @@
     });
 
     CreateChannels(0, 0);
-    std::string err;
-    ASSERT_TRUE(channel1_->SetRemoteContent(&remote, SdpType::kOffer, err))
-        << err;
-    ASSERT_TRUE(channel1_->SetLocalContent(&local, SdpType::kAnswer, err))
-        << err;
+    ASSERT_TRUE(channel1_->SetRemoteContent(&remote, SdpType::kOffer).ok());
+    ASSERT_TRUE(channel1_->SetLocalContent(&local, SdpType::kAnswer).ok());
 
     EXPECT_THAT(media_receive_channel1_impl()->recv_extensions(),
                 ElementsAre(AllOf(Field("id", &RtpExtension::id, 1),
@@ -713,9 +706,10 @@
     });
 
     CreateChannels(0, 0);
-    std::string err;
-    EXPECT_FALSE(channel1_->SetLocalContent(&local, SdpType::kOffer, err));
-    EXPECT_THAT(err, ::testing::HasSubstr("Duplicate RTP extension ID"));
+    RTCError error = channel1_->SetLocalContent(&local, SdpType::kOffer);
+    EXPECT_FALSE(error.ok());
+    EXPECT_THAT(error.message(),
+                ::testing::HasSubstr("Duplicate RTP extension ID"));
   }
 
   void TestInvalidRtpHeaderExtensionIds() {
@@ -726,9 +720,9 @@
     });
 
     CreateChannels(0, 0);
-    std::string err;
-    EXPECT_FALSE(channel1_->SetLocalContent(&local, SdpType::kOffer, err));
-    EXPECT_THAT(err, ::testing::HasSubstr("Bad RTP extension ID"));
+    RTCError error = channel1_->SetLocalContent(&local, SdpType::kOffer);
+    EXPECT_FALSE(error.ok());
+    EXPECT_THAT(error.message(), ::testing::HasSubstr("Bad RTP extension ID"));
   }
 
   void TestRtpHeaderExtensionIdReassignment() {
@@ -739,17 +733,18 @@
     });
 
     CreateChannels(0, 0);
-    std::string err;
-    ASSERT_TRUE(channel1_->SetLocalContent(&local, SdpType::kOffer, err));
+    ASSERT_TRUE(channel1_->SetLocalContent(&local, SdpType::kOffer).ok());
 
     typename T::Content local_updated;
     CreateContent(/*flags=*/0, kPcmuCodec, kH264Codec, &local_updated);
     local_updated.set_rtp_header_extensions({
         RtpExtension(RtpExtension::kVideoRotationUri, 1),
     });
-    EXPECT_FALSE(
-        channel1_->SetLocalContent(&local_updated, SdpType::kOffer, err));
-    EXPECT_THAT(err, ::testing::HasSubstr("RTP extension ID reassignment"));
+    RTCError error =
+        channel1_->SetLocalContent(&local_updated, SdpType::kOffer);
+    EXPECT_FALSE(error.ok());
+    EXPECT_THAT(error.message(),
+                ::testing::HasSubstr("RTP extension ID reassignment"));
   }
 
   void TestRtpHeaderExtensionIdHistoryReassignment() {
@@ -760,22 +755,23 @@
     });
 
     CreateChannels(0, 0);
-    std::string err;
-    ASSERT_TRUE(channel1_->SetLocalContent(&local, SdpType::kOffer, err));
+    ASSERT_TRUE(channel1_->SetLocalContent(&local, SdpType::kOffer).ok());
 
     typename T::Content local_empty;
     CreateContent(/*flags=*/0, kPcmuCodec, kH264Codec, &local_empty);
     local_empty.set_rtp_header_extensions({});
-    ASSERT_TRUE(channel1_->SetLocalContent(&local_empty, SdpType::kOffer, err));
+    ASSERT_TRUE(channel1_->SetLocalContent(&local_empty, SdpType::kOffer).ok());
 
     typename T::Content local_updated;
     CreateContent(/*flags=*/0, kPcmuCodec, kH264Codec, &local_updated);
     local_updated.set_rtp_header_extensions({
         RtpExtension(RtpExtension::kVideoRotationUri, 1),
     });
-    EXPECT_FALSE(
-        channel1_->SetLocalContent(&local_updated, SdpType::kOffer, err));
-    EXPECT_THAT(err, ::testing::HasSubstr("RTP extension ID reassignment"));
+    RTCError error =
+        channel1_->SetLocalContent(&local_updated, SdpType::kOffer);
+    EXPECT_FALSE(error.ok());
+    EXPECT_THAT(error.message(),
+                ::testing::HasSubstr("RTP extension ID reassignment"));
   }
 
   // Test that SetLocalContent and SetRemoteContent properly configure
@@ -789,12 +785,12 @@
     auto offer_enum = offer ? (T::Content::kSession) : (T::Content::kNo);
     auto answer_enum = answer ? (T::Content::kSession) : (T::Content::kNo);
     content.set_extmap_allow_mixed_enum(offer_enum);
-    std::string err;
-    EXPECT_TRUE(channel1_->SetLocalContent(&content, SdpType::kOffer, err));
+    EXPECT_TRUE(channel1_->SetLocalContent(&content, SdpType::kOffer).ok());
     content.set_extmap_allow_mixed_enum(answer_enum);
-    EXPECT_TRUE(channel1_->SetRemoteContent(&content, SdpType::kAnswer, err));
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content, SdpType::kAnswer).ok());
     EXPECT_EQ(answer, media_send_channel1_impl()->ExtmapAllowMixed());
   }
+
   void TestSetContentsExtmapAllowMixedCallee(bool offer, bool answer) {
     // For a callee, SetRemoteContent() is called first with an offer and next
     // SetLocalContent() is called with the answer.
@@ -804,10 +800,9 @@
     auto offer_enum = offer ? (T::Content::kSession) : (T::Content::kNo);
     auto answer_enum = answer ? (T::Content::kSession) : (T::Content::kNo);
     content.set_extmap_allow_mixed_enum(offer_enum);
-    std::string err;
-    EXPECT_TRUE(channel1_->SetRemoteContent(&content, SdpType::kOffer, err));
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content, SdpType::kOffer).ok());
     content.set_extmap_allow_mixed_enum(answer_enum);
-    EXPECT_TRUE(channel1_->SetLocalContent(&content, SdpType::kAnswer, err));
+    EXPECT_TRUE(channel1_->SetLocalContent(&content, SdpType::kAnswer).ok());
     EXPECT_EQ(answer, media_send_channel1()->ExtmapAllowMixed());
   }
 
@@ -816,11 +811,10 @@
   void TestSetContentsNullOffer() {
     CreateChannels(0, 0);
     typename T::Content content;
-    std::string err;
-    EXPECT_TRUE(channel1_->SetLocalContent(&content, SdpType::kOffer, err));
+    EXPECT_TRUE(channel1_->SetLocalContent(&content, SdpType::kOffer).ok());
     CreateContent(0, kPcmuCodec, kH264Codec, &content);
     EXPECT_EQ(0U, media_send_channel1_impl()->send_codecs().size());
-    EXPECT_TRUE(channel1_->SetRemoteContent(&content, SdpType::kAnswer, err));
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content, SdpType::kAnswer).ok());
     ASSERT_EQ(1U, media_send_channel1_impl()->send_codecs().size());
     EXPECT_EQ(content.codecs()[0],
               media_send_channel1_impl()->send_codecs()[0]);
@@ -834,13 +828,12 @@
     CreateContent(0, kPcmuCodec, kH264Codec, &content);
     // Both sides agree on mux. Should no longer be a separate RTCP channel.
     content.set_rtcp_mux(true);
-    std::string err;
-    EXPECT_TRUE(channel1_->SetLocalContent(&content, SdpType::kOffer, err));
-    EXPECT_TRUE(channel1_->SetRemoteContent(&content, SdpType::kAnswer, err));
+    EXPECT_TRUE(channel1_->SetLocalContent(&content, SdpType::kOffer).ok());
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content, SdpType::kAnswer).ok());
     // Only initiator supports mux. Should still have a separate RTCP channel.
-    EXPECT_TRUE(channel2_->SetLocalContent(&content, SdpType::kOffer, err));
+    EXPECT_TRUE(channel2_->SetLocalContent(&content, SdpType::kOffer).ok());
     content.set_rtcp_mux(false);
-    EXPECT_TRUE(channel2_->SetRemoteContent(&content, SdpType::kAnswer, err));
+    EXPECT_TRUE(channel2_->SetRemoteContent(&content, SdpType::kAnswer).ok());
   }
 
   // Test that SetLocalContent and SetRemoteContent properly set RTCP
@@ -851,25 +844,24 @@
     CreateContent(0, kPcmuCodec, kH264Codec, &content);
     // Both sides agree on reduced size.
     content.set_rtcp_reduced_size(true);
-    std::string err;
     // The RTCP mode is a send property and should be configured based on
     // the remote content and not the local content.
-    EXPECT_TRUE(channel1_->SetLocalContent(&content, SdpType::kOffer, err));
+    EXPECT_TRUE(channel1_->SetLocalContent(&content, SdpType::kOffer).ok());
     EXPECT_EQ(media_receive_channel1_impl()->RtcpMode(),
               webrtc::RtcpMode::kCompound);
-    EXPECT_TRUE(channel1_->SetRemoteContent(&content, SdpType::kAnswer, err));
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content, SdpType::kAnswer).ok());
     EXPECT_EQ(media_receive_channel1_impl()->RtcpMode(),
               webrtc::RtcpMode::kReducedSize);
     // Only initiator supports reduced size.
-    EXPECT_TRUE(channel2_->SetLocalContent(&content, SdpType::kOffer, err));
+    EXPECT_TRUE(channel2_->SetLocalContent(&content, SdpType::kOffer).ok());
     EXPECT_EQ(media_receive_channel2_impl()->RtcpMode(),
               webrtc::RtcpMode::kCompound);
     content.set_rtcp_reduced_size(false);
-    EXPECT_TRUE(channel2_->SetRemoteContent(&content, SdpType::kAnswer, err));
+    EXPECT_TRUE(channel2_->SetRemoteContent(&content, SdpType::kAnswer).ok());
     EXPECT_EQ(media_receive_channel2_impl()->RtcpMode(),
               webrtc::RtcpMode::kCompound);
     // Peer renegotiates without reduced size.
-    EXPECT_TRUE(channel1_->SetRemoteContent(&content, SdpType::kAnswer, err));
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content, SdpType::kAnswer).ok());
     EXPECT_EQ(media_receive_channel1_impl()->RtcpMode(),
               webrtc::RtcpMode::kCompound);
   }
@@ -893,21 +885,19 @@
     typename T::Content content1;
     CreateContent(0, kPcmuCodec, kH264Codec, &content1);
     content1.AddStream(stream1);
-    std::string err;
-    EXPECT_TRUE(channel1_->SetLocalContent(&content1, SdpType::kOffer, err));
+    EXPECT_TRUE(channel1_->SetLocalContent(&content1, SdpType::kOffer).ok());
     channel1_->Enable(true);
     EXPECT_EQ(1u, media_send_channel1_impl()->send_streams().size());
-
-    EXPECT_TRUE(channel2_->SetRemoteContent(&content1, SdpType::kOffer, err));
+    EXPECT_TRUE(channel2_->SetRemoteContent(&content1, SdpType::kOffer).ok());
     EXPECT_EQ(1u, media_receive_channel2_impl()->recv_streams().size());
     ConnectFakeTransports();
 
     // Channel 2 do not send anything.
     typename T::Content content2;
     CreateContent(0, kPcmuCodec, kH264Codec, &content2);
-    EXPECT_TRUE(channel1_->SetRemoteContent(&content2, SdpType::kAnswer, err));
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content2, SdpType::kAnswer).ok());
     EXPECT_EQ(0u, media_receive_channel1_impl()->recv_streams().size());
-    EXPECT_TRUE(channel2_->SetLocalContent(&content2, SdpType::kAnswer, err));
+    EXPECT_TRUE(channel2_->SetLocalContent(&content2, SdpType::kAnswer).ok());
     channel2_->Enable(true);
     EXPECT_EQ(0u, media_send_channel2_impl()->send_streams().size());
 
@@ -919,21 +909,21 @@
     typename T::Content content3;
     CreateContent(0, kPcmuCodec, kH264Codec, &content3);
     content3.AddStream(stream2);
-    EXPECT_TRUE(channel2_->SetLocalContent(&content3, SdpType::kOffer, err));
+    EXPECT_TRUE(channel2_->SetLocalContent(&content3, SdpType::kOffer).ok());
     ASSERT_EQ(1u, media_send_channel2_impl()->send_streams().size());
     EXPECT_EQ(stream2, media_send_channel2_impl()->send_streams()[0]);
 
-    EXPECT_TRUE(channel1_->SetRemoteContent(&content3, SdpType::kOffer, err));
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content3, SdpType::kOffer).ok());
     ASSERT_EQ(1u, media_receive_channel1_impl()->recv_streams().size());
     EXPECT_EQ(stream2, media_receive_channel1_impl()->recv_streams()[0]);
 
     // Channel 1 replies but stop sending stream1.
     typename T::Content content4;
     CreateContent(0, kPcmuCodec, kH264Codec, &content4);
-    EXPECT_TRUE(channel1_->SetLocalContent(&content4, SdpType::kAnswer, err));
+    EXPECT_TRUE(channel1_->SetLocalContent(&content4, SdpType::kAnswer).ok());
     EXPECT_EQ(0u, media_send_channel1_impl()->send_streams().size());
 
-    EXPECT_TRUE(channel2_->SetRemoteContent(&content4, SdpType::kAnswer, err));
+    EXPECT_TRUE(channel2_->SetRemoteContent(&content4, SdpType::kAnswer).ok());
     EXPECT_EQ(0u, media_receive_channel2_impl()->recv_streams().size());
 
     SendCustomRtp2(kSsrc2, 0);
@@ -958,21 +948,23 @@
       EXPECT_FALSE(media_receive_channel1_impl()->playout());
     }
     EXPECT_FALSE(media_send_channel1_impl()->sending());
-    std::string err;
-    EXPECT_TRUE(channel1_->SetLocalContent(&local_media_content1_,
-                                           SdpType::kOffer, err));
+    EXPECT_TRUE(
+        channel1_->SetLocalContent(&local_media_content1_, SdpType::kOffer)
+            .ok());
     if (verify_playout_) {
       EXPECT_TRUE(media_receive_channel1_impl()->playout());
     }
     EXPECT_FALSE(media_send_channel1_impl()->sending());
-    EXPECT_TRUE(channel2_->SetRemoteContent(&local_media_content1_,
-                                            SdpType::kOffer, err));
+    EXPECT_TRUE(
+        channel2_->SetRemoteContent(&local_media_content1_, SdpType::kOffer)
+            .ok());
     if (verify_playout_) {
       EXPECT_FALSE(media_receive_channel2_impl()->playout());
     }
     EXPECT_FALSE(media_send_channel2_impl()->sending());
-    EXPECT_TRUE(channel2_->SetLocalContent(&local_media_content2_,
-                                           SdpType::kAnswer, err));
+    EXPECT_TRUE(
+        channel2_->SetLocalContent(&local_media_content2_, SdpType::kAnswer)
+            .ok());
     if (verify_playout_) {
       EXPECT_FALSE(media_receive_channel2_impl()->playout());
     }
@@ -992,8 +984,9 @@
       EXPECT_TRUE(media_receive_channel2_impl()->playout());
     }
     EXPECT_TRUE(media_send_channel2_impl()->sending());
-    EXPECT_TRUE(channel1_->SetRemoteContent(&local_media_content2_,
-                                            SdpType::kAnswer, err));
+    EXPECT_TRUE(
+        channel1_->SetRemoteContent(&local_media_content2_, SdpType::kAnswer)
+            .ok());
     if (verify_playout_) {
       EXPECT_TRUE(media_receive_channel1_impl()->playout());
     }
@@ -1023,12 +1016,11 @@
     }
     EXPECT_FALSE(media_send_channel2_impl()->sending());
 
-    std::string err;
-    EXPECT_TRUE(channel1_->SetLocalContent(&content1, SdpType::kOffer, err));
-    EXPECT_TRUE(channel2_->SetRemoteContent(&content1, SdpType::kOffer, err));
-    EXPECT_TRUE(channel2_->SetLocalContent(&content2, SdpType::kPrAnswer, err));
+    EXPECT_TRUE(channel1_->SetLocalContent(&content1, SdpType::kOffer).ok());
+    EXPECT_TRUE(channel2_->SetRemoteContent(&content1, SdpType::kOffer).ok());
+    EXPECT_TRUE(channel2_->SetLocalContent(&content2, SdpType::kPrAnswer).ok());
     EXPECT_TRUE(
-        channel1_->SetRemoteContent(&content2, SdpType::kPrAnswer, err));
+        channel1_->SetRemoteContent(&content2, SdpType::kPrAnswer).ok());
     ConnectFakeTransports();
 
     if (verify_playout_) {
@@ -1042,9 +1034,9 @@
 
     // Update `content2` to be RecvOnly.
     content2.set_direction(RtpTransceiverDirection::kRecvOnly);
-    EXPECT_TRUE(channel2_->SetLocalContent(&content2, SdpType::kPrAnswer, err));
+    EXPECT_TRUE(channel2_->SetLocalContent(&content2, SdpType::kPrAnswer).ok());
     EXPECT_TRUE(
-        channel1_->SetRemoteContent(&content2, SdpType::kPrAnswer, err));
+        channel1_->SetRemoteContent(&content2, SdpType::kPrAnswer).ok());
 
     if (verify_playout_) {
       EXPECT_TRUE(media_receive_channel1_impl()->playout());
@@ -1057,8 +1049,8 @@
 
     // Update `content2` to be SendRecv.
     content2.set_direction(RtpTransceiverDirection::kSendRecv);
-    EXPECT_TRUE(channel2_->SetLocalContent(&content2, SdpType::kAnswer, err));
-    EXPECT_TRUE(channel1_->SetRemoteContent(&content2, SdpType::kAnswer, err));
+    EXPECT_TRUE(channel2_->SetLocalContent(&content2, SdpType::kAnswer).ok());
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content2, SdpType::kAnswer).ok());
 
     if (verify_playout_) {
       EXPECT_TRUE(media_receive_channel1_impl()->playout());
@@ -1072,11 +1064,11 @@
     // Update `content2` to be inactive on the receiver while sending at the
     // sender.
     content2.set_direction(RtpTransceiverDirection::kInactive);
-    EXPECT_TRUE(channel1_->SetLocalContent(&content1, SdpType::kOffer, err));
-    EXPECT_TRUE(channel2_->SetRemoteContent(&content1, SdpType::kOffer, err));
-    EXPECT_TRUE(channel2_->SetLocalContent(&content2, SdpType::kAnswer, err));
+    EXPECT_TRUE(channel1_->SetLocalContent(&content1, SdpType::kOffer).ok());
+    EXPECT_TRUE(channel2_->SetRemoteContent(&content1, SdpType::kOffer).ok());
+    EXPECT_TRUE(channel2_->SetLocalContent(&content2, SdpType::kAnswer).ok());
     content2.set_direction(RtpTransceiverDirection::kRecvOnly);
-    EXPECT_TRUE(channel1_->SetRemoteContent(&content2, SdpType::kAnswer, err));
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content2, SdpType::kAnswer).ok());
     if (verify_playout_) {
       EXPECT_FALSE(media_receive_channel2_impl()->playout());
     }
@@ -1084,10 +1076,10 @@
 
     // Re-enable `content2`.
     content2.set_direction(RtpTransceiverDirection::kSendRecv);
-    EXPECT_TRUE(channel1_->SetLocalContent(&content1, SdpType::kOffer, err));
-    EXPECT_TRUE(channel2_->SetRemoteContent(&content1, SdpType::kOffer, err));
-    EXPECT_TRUE(channel2_->SetLocalContent(&content2, SdpType::kAnswer, err));
-    EXPECT_TRUE(channel1_->SetRemoteContent(&content2, SdpType::kAnswer, err));
+    EXPECT_TRUE(channel1_->SetLocalContent(&content1, SdpType::kOffer).ok());
+    EXPECT_TRUE(channel2_->SetRemoteContent(&content1, SdpType::kOffer).ok());
+    EXPECT_TRUE(channel2_->SetLocalContent(&content2, SdpType::kAnswer).ok());
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content2, SdpType::kAnswer).ok());
     if (verify_playout_) {
       EXPECT_TRUE(media_receive_channel2_impl()->playout());
     }
@@ -1379,39 +1371,37 @@
   void TestSetContentFailure() {
     CreateChannels(0, 0);
 
-    std::string err;
     std::unique_ptr<typename T::Content> content(
         CreateMediaContentWithStream(1));
 
     media_receive_channel1_impl()->set_fail_set_recv_codecs(true);
     EXPECT_FALSE(
-        channel1_->SetLocalContent(content.get(), SdpType::kOffer, err));
+        channel1_->SetLocalContent(content.get(), SdpType::kOffer).ok());
     EXPECT_FALSE(
-        channel1_->SetLocalContent(content.get(), SdpType::kAnswer, err));
+        channel1_->SetLocalContent(content.get(), SdpType::kAnswer).ok());
 
     media_send_channel1_impl()->set_fail_set_send_codecs(true);
     EXPECT_FALSE(
-        channel1_->SetRemoteContent(content.get(), SdpType::kOffer, err));
+        channel1_->SetRemoteContent(content.get(), SdpType::kOffer).ok());
 
     media_send_channel1_impl()->set_fail_set_send_codecs(true);
     EXPECT_FALSE(
-        channel1_->SetRemoteContent(content.get(), SdpType::kAnswer, err));
+        channel1_->SetRemoteContent(content.get(), SdpType::kAnswer).ok());
   }
 
   void TestSendTwoOffers() {
     CreateChannels(0, 0);
 
-    std::string err;
     std::unique_ptr<typename T::Content> content1(
         CreateMediaContentWithStream(1));
     EXPECT_TRUE(
-        channel1_->SetLocalContent(content1.get(), SdpType::kOffer, err));
+        channel1_->SetLocalContent(content1.get(), SdpType::kOffer).ok());
     EXPECT_TRUE(media_send_channel1_impl()->HasSendStream(1));
 
     std::unique_ptr<typename T::Content> content2(
         CreateMediaContentWithStream(2));
     EXPECT_TRUE(
-        channel1_->SetLocalContent(content2.get(), SdpType::kOffer, err));
+        channel1_->SetLocalContent(content2.get(), SdpType::kOffer).ok());
     EXPECT_FALSE(media_send_channel1_impl()->HasSendStream(1));
     EXPECT_TRUE(media_send_channel1_impl()->HasSendStream(2));
   }
@@ -1419,17 +1409,16 @@
   void TestReceiveTwoOffers() {
     CreateChannels(0, 0);
 
-    std::string err;
     std::unique_ptr<typename T::Content> content1(
         CreateMediaContentWithStream(1));
     EXPECT_TRUE(
-        channel1_->SetRemoteContent(content1.get(), SdpType::kOffer, err));
+        channel1_->SetRemoteContent(content1.get(), SdpType::kOffer).ok());
     EXPECT_TRUE(media_receive_channel1_impl()->HasRecvStream(1));
 
     std::unique_ptr<typename T::Content> content2(
         CreateMediaContentWithStream(2));
     EXPECT_TRUE(
-        channel1_->SetRemoteContent(content2.get(), SdpType::kOffer, err));
+        channel1_->SetRemoteContent(content2.get(), SdpType::kOffer).ok());
     EXPECT_FALSE(media_receive_channel1_impl()->HasRecvStream(1));
     EXPECT_TRUE(media_receive_channel1_impl()->HasRecvStream(2));
   }
@@ -1437,19 +1426,18 @@
   void TestSendPrAnswer() {
     CreateChannels(0, 0);
 
-    std::string err;
     // Receive offer
     std::unique_ptr<typename T::Content> content1(
         CreateMediaContentWithStream(1));
     EXPECT_TRUE(
-        channel1_->SetRemoteContent(content1.get(), SdpType::kOffer, err));
+        channel1_->SetRemoteContent(content1.get(), SdpType::kOffer).ok());
     EXPECT_TRUE(media_receive_channel1_impl()->HasRecvStream(1));
 
     // Send PR answer
     std::unique_ptr<typename T::Content> content2(
         CreateMediaContentWithStream(2));
     EXPECT_TRUE(
-        channel1_->SetLocalContent(content2.get(), SdpType::kPrAnswer, err));
+        channel1_->SetLocalContent(content2.get(), SdpType::kPrAnswer).ok());
     EXPECT_TRUE(media_receive_channel1_impl()->HasRecvStream(1));
     EXPECT_TRUE(media_send_channel1_impl()->HasSendStream(2));
 
@@ -1457,7 +1445,7 @@
     std::unique_ptr<typename T::Content> content3(
         CreateMediaContentWithStream(3));
     EXPECT_TRUE(
-        channel1_->SetLocalContent(content3.get(), SdpType::kAnswer, err));
+        channel1_->SetLocalContent(content3.get(), SdpType::kAnswer).ok());
     EXPECT_TRUE(media_receive_channel1_impl()->HasRecvStream(1));
     EXPECT_FALSE(media_send_channel1_impl()->HasSendStream(2));
     EXPECT_TRUE(media_send_channel1_impl()->HasSendStream(3));
@@ -1466,19 +1454,18 @@
   void TestReceivePrAnswer() {
     CreateChannels(0, 0);
 
-    std::string err;
     // Send offer
     std::unique_ptr<typename T::Content> content1(
         CreateMediaContentWithStream(1));
     EXPECT_TRUE(
-        channel1_->SetLocalContent(content1.get(), SdpType::kOffer, err));
+        channel1_->SetLocalContent(content1.get(), SdpType::kOffer).ok());
     EXPECT_TRUE(media_send_channel1_impl()->HasSendStream(1));
 
     // Receive PR answer
     std::unique_ptr<typename T::Content> content2(
         CreateMediaContentWithStream(2));
     EXPECT_TRUE(
-        channel1_->SetRemoteContent(content2.get(), SdpType::kPrAnswer, err));
+        channel1_->SetRemoteContent(content2.get(), SdpType::kPrAnswer).ok());
     EXPECT_TRUE(media_send_channel1_impl()->HasSendStream(1));
     EXPECT_TRUE(media_receive_channel1_impl()->HasRecvStream(2));
 
@@ -1486,7 +1473,7 @@
     std::unique_ptr<typename T::Content> content3(
         CreateMediaContentWithStream(3));
     EXPECT_TRUE(
-        channel1_->SetRemoteContent(content3.get(), SdpType::kAnswer, err));
+        channel1_->SetRemoteContent(content3.get(), SdpType::kAnswer).ok());
     EXPECT_TRUE(media_send_channel1_impl()->HasSendStream(1));
     EXPECT_FALSE(media_receive_channel1_impl()->HasRecvStream(2));
     EXPECT_TRUE(media_receive_channel1_impl()->HasRecvStream(3));
@@ -1530,9 +1517,9 @@
 
   void DefaultMaxBitrateIsUnlimited() {
     CreateChannels(0, 0);
-    std::string err;
-    EXPECT_TRUE(channel1_->SetLocalContent(&local_media_content1_,
-                                           SdpType::kOffer, err));
+    EXPECT_TRUE(
+        channel1_->SetLocalContent(&local_media_content1_, SdpType::kOffer)
+            .ok());
     EXPECT_EQ(media_send_channel1_impl()->max_bps(), -1);
     VerifyMaxBitrate(media_send_channel1()->GetRtpSendParameters(kSsrc1),
                      std::nullopt);
@@ -1605,14 +1592,13 @@
     CreateChannels(0, 0);
     typename T::Content content1, content2, content3;
     CreateSimulcastContent({"f", "h", "q"}, &content1);
-    std::string err;
-    EXPECT_TRUE(channel1_->SetLocalContent(&content1, SdpType::kOffer, err));
+    EXPECT_TRUE(channel1_->SetLocalContent(&content1, SdpType::kOffer).ok());
     VerifySimulcastStreamParams(content1.streams()[0], channel1_.get());
     StreamParams stream1 = channel1_->local_streams()[0];
 
     // Create a similar offer. SetLocalContent should not remove and add.
     CreateSimulcastContent({"f", "h", "q"}, &content2);
-    EXPECT_TRUE(channel1_->SetLocalContent(&content2, SdpType::kOffer, err));
+    EXPECT_TRUE(channel1_->SetLocalContent(&content2, SdpType::kOffer).ok());
     VerifySimulcastStreamParams(content2.streams()[0], channel1_.get());
     StreamParams stream2 = channel1_->local_streams()[0];
     // Check that the streams are identical (SSRCs didn't change).
@@ -1620,7 +1606,7 @@
 
     // Create third offer that has same RIDs in different order.
     CreateSimulcastContent({"f", "q", "h"}, &content3);
-    EXPECT_TRUE(channel1_->SetLocalContent(&content3, SdpType::kOffer, err));
+    EXPECT_TRUE(channel1_->SetLocalContent(&content3, SdpType::kOffer).ok());
     VerifySimulcastStreamParams(content3.streams()[0], channel1_.get());
   }
 
@@ -2347,8 +2333,7 @@
 
   CreateChannels(0, 0);
 
-  std::string err;
-  EXPECT_TRUE(channel1_->SetLocalContent(&video, SdpType::kOffer, err));
+  EXPECT_TRUE(channel1_->SetLocalContent(&video, SdpType::kOffer).ok());
   EXPECT_THAT(media_send_channel1_impl()->send_codecs(), testing::IsEmpty());
   ASSERT_THAT(media_receive_channel1_impl()->recv_codecs(), testing::SizeIs(2));
   EXPECT_TRUE(
@@ -2370,9 +2355,7 @@
 
   CreateChannels(0, 0);
 
-  std::string err;
-  EXPECT_TRUE(channel1_->SetRemoteContent(&video, SdpType::kOffer, err));
-  EXPECT_TRUE(err.empty());
+  EXPECT_TRUE(channel1_->SetRemoteContent(&video, SdpType::kOffer).ok());
   EXPECT_THAT(media_receive_channel1_impl()->recv_codecs(), testing::IsEmpty());
   ASSERT_THAT(media_send_channel1_impl()->send_codecs(), testing::SizeIs(2));
   EXPECT_TRUE(media_send_channel1_impl()->send_codecs()[0].Matches(kVp8Codec));
@@ -2392,11 +2375,8 @@
 
   CreateChannels(0, 0);
 
-  std::string err;
-  EXPECT_TRUE(channel1_->SetLocalContent(&video, SdpType::kOffer, err));
-  EXPECT_TRUE(err.empty());
-  EXPECT_TRUE(channel1_->SetRemoteContent(&video, SdpType::kAnswer, err));
-  EXPECT_TRUE(err.empty());
+  EXPECT_TRUE(channel1_->SetLocalContent(&video, SdpType::kOffer).ok());
+  EXPECT_TRUE(channel1_->SetRemoteContent(&video, SdpType::kAnswer).ok());
   ASSERT_THAT(media_receive_channel1_impl()->recv_codecs(), testing::SizeIs(2));
   EXPECT_TRUE(
       media_receive_channel1_impl()->recv_codecs()[0].Matches(kVp8Codec));
@@ -2426,9 +2406,8 @@
 
   CreateChannels(0, 0);
 
-  std::string err;
-  EXPECT_TRUE(channel1_->SetRemoteContent(&remote_video, SdpType::kOffer, err));
-  EXPECT_TRUE(channel1_->SetLocalContent(&local_video, SdpType::kAnswer, err));
+  EXPECT_TRUE(channel1_->SetRemoteContent(&remote_video, SdpType::kOffer).ok());
+  EXPECT_TRUE(channel1_->SetLocalContent(&local_video, SdpType::kAnswer).ok());
   ASSERT_THAT(media_receive_channel1_impl()->recv_codecs(), testing::SizeIs(1));
   EXPECT_EQ(media_receive_channel1_impl()->recv_codecs()[0].packetization,
             std::nullopt);
@@ -2448,10 +2427,9 @@
 
   CreateChannels(0, 0);
 
-  std::string err;
-  EXPECT_TRUE(channel1_->SetLocalContent(&local_video, SdpType::kOffer, err));
+  EXPECT_TRUE(channel1_->SetLocalContent(&local_video, SdpType::kOffer).ok());
   EXPECT_TRUE(
-      channel1_->SetRemoteContent(&remote_video, SdpType::kAnswer, err));
+      channel1_->SetRemoteContent(&remote_video, SdpType::kAnswer).ok());
   ASSERT_THAT(media_receive_channel1_impl()->recv_codecs(), testing::SizeIs(1));
   EXPECT_EQ(media_receive_channel1_impl()->recv_codecs()[0].packetization,
             std::nullopt);
@@ -2473,12 +2451,9 @@
 
   CreateChannels(0, 0);
 
-  std::string err;
-  EXPECT_TRUE(channel1_->SetLocalContent(&local_video, SdpType::kOffer, err));
-  EXPECT_TRUE(err.empty());
+  EXPECT_TRUE(channel1_->SetLocalContent(&local_video, SdpType::kOffer).ok());
   EXPECT_FALSE(
-      channel1_->SetRemoteContent(&remote_video, SdpType::kAnswer, err));
-  EXPECT_FALSE(err.empty());
+      channel1_->SetRemoteContent(&remote_video, SdpType::kAnswer).ok());
   ASSERT_THAT(media_receive_channel1_impl()->recv_codecs(), testing::SizeIs(1));
   EXPECT_EQ(media_receive_channel1_impl()->recv_codecs()[0].packetization,
             webrtc::kPacketizationParamRaw);
@@ -2497,11 +2472,8 @@
 
   CreateChannels(0, 0);
 
-  std::string err;
-  EXPECT_TRUE(channel1_->SetRemoteContent(&remote_video, SdpType::kOffer, err));
-  EXPECT_TRUE(err.empty());
-  EXPECT_FALSE(channel1_->SetLocalContent(&local_video, SdpType::kAnswer, err));
-  EXPECT_FALSE(err.empty());
+  EXPECT_TRUE(channel1_->SetRemoteContent(&remote_video, SdpType::kOffer).ok());
+  EXPECT_FALSE(channel1_->SetLocalContent(&local_video, SdpType::kAnswer).ok());
   EXPECT_THAT(media_receive_channel1_impl()->recv_codecs(), testing::IsEmpty());
   ASSERT_THAT(media_send_channel1_impl()->send_codecs(), testing::SizeIs(1));
   EXPECT_EQ(media_send_channel1_impl()->send_codecs()[0].packetization,
@@ -2523,10 +2495,8 @@
   remote.set_codecs({vp8_foo, vp9});
 
   CreateChannels(0, 0);
-  std::string err;
-  ASSERT_TRUE(channel1_->SetLocalContent(&local, SdpType::kOffer, err)) << err;
-  ASSERT_TRUE(channel1_->SetRemoteContent(&remote, SdpType::kAnswer, err))
-      << err;
+  ASSERT_TRUE(channel1_->SetLocalContent(&local, SdpType::kOffer).ok());
+  ASSERT_TRUE(channel1_->SetRemoteContent(&remote, SdpType::kAnswer).ok());
 
   EXPECT_THAT(
       media_receive_channel1_impl()->recv_codecs(),
@@ -2559,10 +2529,8 @@
   remote.set_codecs({vp8_foo, vp8_bar, vp9_foo});
 
   CreateChannels(0, 0);
-  std::string err;
-  ASSERT_TRUE(channel1_->SetRemoteContent(&remote, SdpType::kOffer, err))
-      << err;
-  ASSERT_TRUE(channel1_->SetLocalContent(&local, SdpType::kAnswer, err)) << err;
+  ASSERT_TRUE(channel1_->SetRemoteContent(&remote, SdpType::kOffer).ok());
+  ASSERT_TRUE(channel1_->SetLocalContent(&local, SdpType::kAnswer).ok());
 
   EXPECT_THAT(
       media_receive_channel1_impl()->recv_codecs(),
@@ -2595,10 +2563,8 @@
   remote.set_codecs({vp8_raw, vp8});
 
   CreateChannels(0, 0);
-  std::string err;
-  ASSERT_TRUE(channel1_->SetLocalContent(&local, SdpType::kOffer, err)) << err;
-  ASSERT_TRUE(channel1_->SetRemoteContent(&remote, SdpType::kAnswer, err))
-      << err;
+  ASSERT_TRUE(channel1_->SetLocalContent(&local, SdpType::kOffer).ok());
+  ASSERT_TRUE(channel1_->SetRemoteContent(&remote, SdpType::kAnswer).ok());
 
   EXPECT_THAT(media_receive_channel1_impl()->recv_codecs(),
               UnorderedElementsAre(
@@ -2627,10 +2593,8 @@
   remote.set_codecs({vp8, vp8_raw});
 
   CreateChannels(0, 0);
-  std::string err;
-  ASSERT_TRUE(channel1_->SetRemoteContent(&remote, SdpType::kOffer, err))
-      << err;
-  ASSERT_TRUE(channel1_->SetLocalContent(&local, SdpType::kAnswer, err)) << err;
+  ASSERT_TRUE(channel1_->SetRemoteContent(&remote, SdpType::kOffer).ok());
+  ASSERT_TRUE(channel1_->SetLocalContent(&local, SdpType::kAnswer).ok());
 
   EXPECT_THAT(
       media_receive_channel1_impl()->recv_codecs(),
diff --git a/pc/rtp_transceiver.cc b/pc/rtp_transceiver.cc
index 6357644..0105748 100644
--- a/pc/rtp_transceiver.cc
+++ b/pc/rtp_transceiver.cc
@@ -1299,33 +1299,31 @@
   return channel_->SetRtpTransport(rtp_transport);
 }
 
-bool RtpTransceiver::SetChannelLocalContent(
+RTCError RtpTransceiver::SetChannelLocalContent(
     const MediaContentDescription* content,
-    SdpType type,
-    std::string& error_desc) {
+    SdpType type) {
   RTC_DCHECK_RUN_ON(context()->signaling_thread());
   return SetChannelContent([&]() {
     RTC_DCHECK_RUN_ON(context()->worker_thread());
-    return channel_->SetLocalContent(content, type, error_desc);
+    return channel_->SetLocalContent(content, type);
   });
 }
 
-bool RtpTransceiver::SetChannelRemoteContent(
+RTCError RtpTransceiver::SetChannelRemoteContent(
     const MediaContentDescription* content,
-    SdpType type,
-    std::string& error_desc) {
+    SdpType type) {
   RTC_DCHECK_RUN_ON(context()->signaling_thread());
   return SetChannelContent([&]() {
     RTC_DCHECK_RUN_ON(context()->worker_thread());
-    return channel_->SetRemoteContent(content, type, error_desc);
+    return channel_->SetRemoteContent(content, type);
   });
 }
 
-bool RtpTransceiver::SetChannelContent(
-    absl::AnyInvocable<bool() &&> set_content) {
+RTCError RtpTransceiver::SetChannelContent(
+    absl::AnyInvocable<RTCError() &&> set_content) {
   RTC_DCHECK_RUN_ON(context()->signaling_thread());
   if (!channel_) {
-    return false;
+    return RTCError::InvalidState() << "No channel";
   }
 
   struct SenderParameters {
@@ -1343,9 +1341,10 @@
 
   // Calls the callback on the worker thread, fetches and returns the
   // RtpParameters for the senders.
-  bool result = context()->worker_thread()->BlockingCall([&]() {
-    if (!std::move(set_content)()) {
-      return false;
+  RTCError result = context()->worker_thread()->BlockingCall([&]() {
+    RTCError error = std::move(set_content)();
+    if (!error.ok()) {
+      return error;
     }
     for (auto& entry : sender_parameters) {
       if (entry.ssrc != 0) {
@@ -1353,7 +1352,7 @@
             channel_->media_send_channel()->GetRtpSendParameters(entry.ssrc);
       }
     }
-    return true;
+    return RTCError::OK();
   });
 
   for (auto& entry : sender_parameters) {
diff --git a/pc/rtp_transceiver.h b/pc/rtp_transceiver.h
index 81e1855..730978b 100644
--- a/pc/rtp_transceiver.h
+++ b/pc/rtp_transceiver.h
@@ -377,12 +377,10 @@
   }
 
   bool SetChannelRtpTransport(RtpTransportInternal* rtp_transport);
-  bool SetChannelLocalContent(const MediaContentDescription* content,
-                              SdpType type,
-                              std::string& error_desc);
-  bool SetChannelRemoteContent(const MediaContentDescription* content,
-                               SdpType type,
-                               std::string& error_desc);
+  RTCError SetChannelLocalContent(const MediaContentDescription* content,
+                                  SdpType type);
+  RTCError SetChannelRemoteContent(const MediaContentDescription* content,
+                                   SdpType type);
   bool SetChannelPayloadTypeDemuxingEnabled(bool enabled);
   void EnableChannel(bool enable);
   const std::vector<StreamParams>& channel_local_streams() const;
@@ -433,7 +431,7 @@
   GetOfferedAndImplementedHeaderExtensions(
       const MediaContentDescription* content) const;
 
-  bool SetChannelContent(absl::AnyInvocable<bool() &&> set_content);
+  RTCError SetChannelContent(absl::AnyInvocable<RTCError() &&> set_content);
 
   const Environment env_;
   // Enforce that this object is created, used and destroyed on one thread.
diff --git a/pc/sdp_offer_answer.cc b/pc/sdp_offer_answer.cc
index cb2ed5c..a182232 100644
--- a/pc/sdp_offer_answer.cc
+++ b/pc/sdp_offer_answer.cc
@@ -5233,13 +5233,12 @@
     // - crbug.com/1157227
     // - crbug.com/1187289
     for (const auto& [transceiver, content] : channels) {
-      std::string error;
-      bool success =
+      RTCError error =
           (source == CS_LOCAL)
-              ? transceiver->SetChannelLocalContent(content, type, error)
-              : transceiver->SetChannelRemoteContent(content, type, error);
-      if (!success) {
-        return LOG_ERROR(RTCError::InvalidParameter() << error);
+              ? transceiver->SetChannelLocalContent(content, type)
+              : transceiver->SetChannelRemoteContent(content, type);
+      if (!error.ok()) {
+        return error;
       }
     }
     // If local and remote are both set, we assume that it's safe to trigger
diff --git a/pc/test/mock_channel_interface.h b/pc/test/mock_channel_interface.h
index 1dc4ddd..b87ab9d 100644
--- a/pc/test/mock_channel_interface.h
+++ b/pc/test/mock_channel_interface.h
@@ -18,6 +18,7 @@
 #include "absl/strings/string_view.h"
 #include "api/jsep.h"
 #include "api/media_types.h"
+#include "api/rtc_error.h"
 #include "media/base/media_channel.h"
 #include "media/base/stream_params.h"
 #include "pc/channel_interface.h"
@@ -72,13 +73,13 @@
               SetPacketReceivedCallback_n,
               (absl::AnyInvocable<void(const RtpPacketReceived&)>),
               (override));
-  MOCK_METHOD(bool,
+  MOCK_METHOD(RTCError,
               SetLocalContent,
-              (const webrtc::MediaContentDescription*, SdpType, std::string&),
+              (const webrtc::MediaContentDescription*, SdpType),
               (override));
-  MOCK_METHOD(bool,
+  MOCK_METHOD(RTCError,
               SetRemoteContent,
-              (const webrtc::MediaContentDescription*, SdpType, std::string&),
+              (const webrtc::MediaContentDescription*, SdpType),
               (override));
   MOCK_METHOD(bool, SetPayloadTypeDemuxingEnabled, (bool), (override));
   MOCK_METHOD(const std::vector<StreamParams>&,