blob: b0599defc8c3bb023a2aa0371e1e513364603d03 [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/typed_codec_vendor.h"
#include <functional>
#include <map>
#include <string>
#include <vector>
#include "absl/base/nullability.h"
#include "absl/strings/match.h"
#include "absl/strings/string_view.h"
#include "api/audio_codecs/audio_format.h"
#include "api/field_trials_view.h"
#include "api/media_types.h"
#include "api/payload_type.h"
#include "api/video_codecs/sdp_video_format.h"
#include "media/base/codec.h"
#include "media/base/codec_list.h"
#include "media/base/media_constants.h"
#include "media/base/media_engine.h"
#include "pc/codec_configuration.h"
#include "rtc_base/checks.h"
#include "rtc_base/containers/flat_set.h"
namespace webrtc {
namespace {
// Create the voice codec configurations. Do not allocate payload types at this
// time.
std::vector<CodecConfiguration> CollectAudioCodecConfigurations(
const std::vector<AudioCodecSpec>& specs) {
std::vector<CodecConfiguration> out;
// Audio RED is handled by the engine, not the factory, and is always
// available for Opus.
bool has_red = true;
// Only generate CN payload types for these clockrates:
std::map<int, bool, std::greater<int>> generate_cn = {{8000, false}};
// Only generate telephone-event payload types for these clockrates:
std::map<int, bool, std::greater<int>> generate_dtmf = {{8000, false},
{48000, false}};
for (const auto& spec : specs) {
if (absl::EqualsIgnoreCase(spec.format.name, kRedCodecName)) {
continue;
}
CodecConfiguration config;
config.codec = CreateAudioCodec(spec.format);
if (spec.info.supports_network_adaption) {
config.codec.AddFeedbackParam(
FeedbackParam(kRtcpFbParamTransportCc, kParamValueEmpty));
}
if (spec.info.allow_comfort_noise) {
// Generate a CN entry if the decoder allows it and we support the
// clockrate.
auto cn = generate_cn.find(spec.format.clockrate_hz);
if (cn != generate_cn.end()) {
cn->second = true;
}
}
// Generate a telephone-event entry if we support the clockrate.
auto dtmf = generate_dtmf.find(spec.format.clockrate_hz);
if (dtmf != generate_dtmf.end()) {
dtmf->second = true;
}
if (has_red && config.codec.name == kOpusCodecName) {
config.resiliency.red = true;
}
out.push_back(config);
}
// Add CN codecs after "proper" audio codecs.
for (const auto& cn : generate_cn) {
if (cn.second) {
CodecConfiguration cn_config;
cn_config.codec = CreateAudioCodec({kCnCodecName, cn.first, 1});
out.push_back(cn_config);
}
}
// Add telephone-event codecs last.
for (const auto& dtmf : generate_dtmf) {
if (dtmf.second) {
CodecConfiguration dtmf_config;
dtmf_config.codec = CreateAudioCodec({kDtmfCodecName, dtmf.first, 1});
out.push_back(dtmf_config);
}
}
return out;
}
std::vector<CodecConfiguration> AudioCodecConfigurationsFromFactory(
const VoiceEngineInterface& voice,
bool is_sender) {
RTC_DCHECK(!is_sender || voice.encoder_factory()) << "No encoder factory";
RTC_DCHECK(is_sender || voice.decoder_factory()) << "No decoder factory";
return CollectAudioCodecConfigurations(
is_sender ? voice.encoder_factory()->GetSupportedEncoders()
: voice.decoder_factory()->GetSupportedDecoders());
}
std::vector<CodecConfiguration> CollectVideoCodecConfigurations(
const std::vector<SdpVideoFormat>& formats,
bool rtx_enabled,
const FieldTrialsView& trials) {
if (formats.empty()) {
return {};
}
bool has_red = false;
bool has_ulpfec = false;
bool has_flexfec = false;
bool has_rtx = false;
for (const auto& format : formats) {
if (absl::EqualsIgnoreCase(format.name, kRedCodecName)) {
has_red = true;
} else if (absl::EqualsIgnoreCase(format.name, kUlpfecCodecName)) {
has_ulpfec = true;
} else if (absl::EqualsIgnoreCase(format.name, kFlexfecCodecName)) {
has_flexfec = true;
} else if (absl::EqualsIgnoreCase(format.name, kRtxCodecName)) {
has_rtx = true;
}
}
std::vector<CodecConfiguration> out;
for (const auto& format : formats) {
Codec codec = CreateVideoCodec(format);
if (codec.IsResiliencyCodec()) {
continue;
}
AddDefaultFeedbackParams(&codec, trials);
CodecConfiguration config;
config.codec = codec;
config.codec.id = PayloadType::NotSet();
if (rtx_enabled && has_rtx) {
Codec::ResiliencyType resiliency_type = codec.GetResiliencyType();
if (resiliency_type != Codec::ResiliencyType::kFlexfec &&
resiliency_type != Codec::ResiliencyType::kUlpfec) {
config.resiliency.rtx = true;
}
}
config.resiliency.red = has_red;
config.resiliency.ulpfec = has_ulpfec;
if (trials.IsEnabled("WebRTC-FlexFEC-03-Advertised")) {
config.resiliency.flexfec = has_flexfec;
}
out.push_back(config);
}
return out;
}
std::vector<CodecConfiguration> VideoCodecConfigurationsFromFactory(
const VideoEngineInterface& video,
bool is_sender,
bool rtx_enabled,
const FieldTrialsView& trials) {
return CollectVideoCodecConfigurations(video.GetSupportedFormats(!is_sender),
rtx_enabled, trials);
}
Codecs CodecsFromConfigurations(
const std::vector<CodecConfiguration>& configurations,
MediaType type) {
Codecs out;
flat_set<std::string> shared_added;
for (const auto& config : configurations) {
out.push_back(config.codec);
if (type == MediaType::AUDIO) {
if (config.resiliency.red && shared_added.insert(kRedCodecName).second) {
out.push_back(CreateAudioCodec({kRedCodecName, 48000, 2}));
}
} else {
if (config.resiliency.rtx) {
out.push_back(CreateVideoCodec(PayloadType::NotSet(), kRtxCodecName));
}
if (config.resiliency.red && shared_added.insert(kRedCodecName).second) {
out.push_back(CreateVideoCodec(kRedCodecName));
// Video RED also gets an RTX codec.
out.push_back(CreateVideoCodec(PayloadType::NotSet(), kRtxCodecName));
}
if (config.resiliency.ulpfec &&
shared_added.insert(kUlpfecCodecName).second) {
out.push_back(CreateVideoCodec(kUlpfecCodecName));
}
if (config.resiliency.flexfec &&
shared_added.insert(kFlexfecCodecName).second) {
out.push_back(CreateVideoCodec(kFlexfecCodecName));
}
}
}
return out;
}
Codecs GetLegacyVideoCodecs(const VideoEngineInterface& video,
bool is_sender,
bool rtx_enabled) {
return is_sender ? video.LegacySendCodecs(rtx_enabled)
: video.LegacyRecvCodecs(rtx_enabled);
}
Codecs GetCodecs(const MediaEngineInterface* media_engine,
MediaType type,
bool is_sender,
bool rtx_enabled) {
const VoiceEngineInterface& voice = media_engine->voice();
const VideoEngineInterface& video = media_engine->video();
// Use current mechanisms for getting codecs from media engine.
return (type == MediaType::AUDIO)
? (is_sender ? voice.LegacySendCodecs() : voice.LegacyRecvCodecs())
: GetLegacyVideoCodecs(video, is_sender, rtx_enabled);
}
} // namespace
TypedCodecVendor::TypedCodecVendor(const MediaEngineInterface* absl_nonnull
media_engine,
MediaType type,
bool is_sender,
bool rtx_enabled,
const FieldTrialsView& trials) {
RTC_DCHECK(media_engine != nullptr);
if (trials.IsEnabled("WebRTC-PayloadTypesInTransport")) {
if (type == MediaType::AUDIO) {
configurations_ =
AudioCodecConfigurationsFromFactory(media_engine->voice(), is_sender);
} else {
configurations_ = VideoCodecConfigurationsFromFactory(
media_engine->video(), is_sender, rtx_enabled, trials);
}
codecs_ = CodecList::CreateFromTrustedData(
CodecsFromConfigurations(configurations_, type));
} else {
codecs_ = CodecList::CreateFromTrustedData(
GetCodecs(media_engine, type, is_sender, rtx_enabled));
}
}
} // namespace webrtc