blob: fd4d2737da4f33c75825eff9f5321f05edfd72ff [file]
/*
* Copyright 2025 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/sdp_payload_type_suggester.h"
#include <map>
#include <string>
#include <utility>
#include "absl/strings/string_view.h"
#include "api/jsep.h"
#include "api/payload_type.h"
#include "api/rtc_error.h"
#include "api/rtp_parameters.h"
#include "call/payload_type.h"
#include "call/payload_type_picker.h"
#include "media/base/codec.h"
#include "media/base/codec_comparators.h"
#include "pc/session_description.h"
#include "rtc_base/checks.h"
#include "rtc_base/thread.h"
#include "rtc_base/trace_event.h"
namespace webrtc {
// Implementation of the SdpPayloadTypeSuggester
RTCErrorOr<PayloadType> SdpPayloadTypeSuggester::SuggestPayloadType(
absl::string_view mid,
const Codec& codec,
bool pick_from_top_of_range) {
RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS();
PayloadTypeRecorder& local_recorder = LookupRecorder(mid, /* local= */ true);
if (pick_from_top_of_range && codec.id.IsSet()) {
RTCErrorOr<Codec> existing = local_recorder.LookupCodec(codec.id);
if (existing.ok()) {
if (MatchesWithReferenceAttributes(existing.value(), codec)) {
return codec.id;
}
} else if (codec.id >= 0 && codec.id <= 127 &&
!payload_type_picker_.IsSeen(codec.id)) {
local_recorder.AddMapping(codec.id, codec);
return codec.id;
}
}
auto local_result = local_recorder.LookupPayloadType(codec);
if (local_result.ok()) {
return local_result;
}
PayloadTypeRecorder& remote_recorder =
LookupRecorder(mid, /* local= */ false);
RTCErrorOr<PayloadType> remote_result =
remote_recorder.LookupPayloadType(codec);
if (remote_result.ok()) {
RTCErrorOr<Codec> local_codec =
local_recorder.LookupCodec(remote_result.value());
if (!local_codec.ok()) {
// Tell the local payload type registry that we've taken this
RTC_DCHECK(local_codec.error().type() == RTCErrorType::INVALID_PARAMETER);
AddLocalMapping(mid, remote_result.value(), codec);
return remote_result;
}
// If we get here, PT is already in use, possibly for something else.
// Fall through to SuggestMapping.
}
RTCErrorOr<PayloadType> suggested_result =
payload_type_picker_.SuggestMapping(codec, &local_recorder,
pick_from_top_of_range);
if (suggested_result.ok()) {
local_recorder.AddMapping(suggested_result.value(), codec);
}
return suggested_result;
}
RTCError SdpPayloadTypeSuggester::AddLocalMapping(absl::string_view mid,
PayloadType payload_type,
const Codec& codec) {
RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS();
PayloadTypeRecorder& recorder = LookupRecorder(mid, /* local= */ true);
return recorder.AddMapping(payload_type, codec);
}
RTCErrorOr<int> SdpPayloadTypeSuggester::SuggestRtpHeaderExtensionId(
absl::string_view mid,
const RtpExtension& extension,
RtpTransceiverIdDomain id_domain) {
RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS();
BundleTypeRecorder& bundle_recorder = LookupBundleRecorder(mid);
RTCErrorOr<int> result = bundle_recorder.header_extensions().LookupId(
extension.uri, extension.encrypt);
if (result.ok()) {
return result;
}
return rtp_header_extension_picker_.SuggestMapping(
extension.uri, extension.encrypt, extension.id, id_domain,
&bundle_recorder.header_extensions());
}
RTCError SdpPayloadTypeSuggester::AddRtpHeaderExtensionMapping(
absl::string_view mid,
const RtpExtension& extension,
bool local) {
RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS();
BundleTypeRecorder& bundle_recorder = LookupBundleRecorder(mid);
rtp_header_extension_picker_.AddMapping(extension.id, extension.uri,
extension.encrypt);
return bundle_recorder.header_extensions().AddMapping(
extension.id, extension.uri, extension.encrypt);
}
RTCError SdpPayloadTypeSuggester::Update(const SessionDescription* description,
bool local,
SdpType type) {
bundle_manager_.Update(description, type);
if (type == SdpType::kAnswer) {
bundle_manager_.Commit();
}
for (const ContentInfo& content : description->contents()) {
if (content.rejected) {
continue;
}
PayloadTypeRecorder& recorder = LookupRecorder(content.mid(), local);
recorder.DisallowRedefinition();
RTCError error;
for (const Codec& codec : content.media_description()->codecs()) {
error = recorder.AddMapping(codec.id, codec);
if (!error.ok()) {
break;
}
}
recorder.ReallowRedefinition();
if (!error.ok()) {
return error;
}
BundleTypeRecorder& bundle_recorder = LookupBundleRecorder(content.mid());
for (const auto& extension :
content.media_description()->rtp_header_extensions()) {
bundle_recorder.header_extensions().AddMapping(
extension.id, extension.uri, extension.encrypt);
}
if (type == SdpType::kAnswer) {
bundle_recorder.header_extensions().Commit();
}
}
return RTCError::OK();
}
PayloadTypeRecorder& SdpPayloadTypeSuggester::LookupRecorder(
absl::string_view mid,
bool local) {
BundleTypeRecorder& recorder = LookupBundleRecorder(mid);
return local ? recorder.local_payload_types()
: recorder.remote_payload_types();
}
SdpPayloadTypeSuggester::BundleTypeRecorder&
SdpPayloadTypeSuggester::LookupBundleRecorder(absl::string_view mid) {
const ContentGroup* group = bundle_manager_.LookupGroupByMid(mid);
std::string transport_mapped_name;
if (group) {
const std::string* group_name = group->FirstContentName();
RTC_CHECK(group_name); // empty groups should be impossible here
transport_mapped_name = *group_name;
} else {
// Not in a group.
transport_mapped_name = mid;
}
if (!recorder_by_mid_.contains(transport_mapped_name)) {
recorder_by_mid_.emplace(std::make_pair(
transport_mapped_name, BundleTypeRecorder(payload_type_picker_, env_)));
}
return recorder_by_mid_.at(transport_mapped_name);
}
} // namespace webrtc