blob: dac3e125947cdc5f8b3c11d955d4f17cc5ef443e [file]
/*
* Copyright 2026 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 "api/webrtc_sdp_planb.h"
#include <cstdint>
#include <string>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/strings/match.h"
#include "absl/strings/string_view.h"
#include "api/media_types.h"
#include "media/base/stream_params.h"
#include "pc/session_description.h"
#include "rtc_base/containers/flat_set.h"
#include "rtc_base/crypto_random.h"
#include "rtc_base/logging.h"
#include "rtc_base/strings/string_builder.h"
namespace webrtc {
namespace {
constexpr absl::string_view kLineBreak = "\r\n";
constexpr absl::string_view kDefaultMsid = "default";
constexpr absl::string_view kNoStreamMsid = "-";
constexpr absl::string_view kMediaStreamSemantic = "WMS";
constexpr absl::string_view kAttributeSsrc = "ssrc";
constexpr absl::string_view kSsrcAttributeMsid = "msid";
bool IsMediaType(const MediaContentDescription* description, MediaType type) {
return description != nullptr && description->type() == type;
}
const ContentInfo* GetFirstMediaContent(const ContentInfos& contents,
MediaType type) {
for (const ContentInfo& content : contents) {
if (IsMediaType(content.media_description(), type)) {
return &content;
}
}
return nullptr;
}
const ContentInfo* LegacyGetFirstAudioContent(const SessionDescription& sdesc) {
return GetFirstMediaContent(sdesc.contents(), MediaType::AUDIO);
}
const ContentInfo* LegacyGetFirstVideoContent(const SessionDescription& sdesc) {
return GetFirstMediaContent(sdesc.contents(), MediaType::VIDEO);
}
void GetMediaStreamIds(const ContentInfo* content,
flat_set<std::string>* labels) {
for (const StreamParams& stream_params :
content->media_description()->streams()) {
for (const std::string& stream_id : stream_params.stream_ids()) {
labels->insert(stream_id);
}
}
}
} // namespace
void CreatePlanBTracksFromSsrcInfos(const std::vector<SsrcInfo>& ssrc_infos,
int msid_signaling,
StreamParamsVec* tracks) {
for (const SsrcInfo& ssrc_info : ssrc_infos) {
// According to https://tools.ietf.org/html/rfc5576#section-6.1, the CNAME
// attribute is mandatory, but we relax that restriction.
if (ssrc_info.cname.empty()) {
RTC_LOG(LS_WARNING) << "CNAME attribute missing for SSRC "
<< ssrc_info.ssrc_id;
}
std::vector<std::string> stream_ids;
absl::string_view track_id;
if (msid_signaling & kMsidSignalingSsrcAttribute) {
// This is the case with Plan B SDP msid signaling.
stream_ids.emplace_back(ssrc_info.stream_id);
track_id = ssrc_info.track_id;
} else if (msid_signaling == kMsidSignalingNotUsed) {
// Since no media streams isn't supported with older SDP signaling, we
// use a default stream id.
stream_ids.emplace_back(kDefaultMsid);
}
auto track_it = absl::c_find_if(
*tracks,
[track_id](const StreamParams& track) { return track.id == track_id; });
if (track_it == tracks->end()) {
// If we don't find an existing track, create a new one.
tracks->push_back(StreamParams());
track_it = tracks->end() - 1;
}
StreamParams& track = *track_it;
track.add_ssrc(ssrc_info.ssrc_id);
track.cname = ssrc_info.cname;
track.set_stream_ids(stream_ids);
track.id = track_id;
}
for (StreamParams& stream : *tracks) {
// If a track ID wasn't populated from the SSRC attributes OR the
// msid attribute, use default/random values. This happens after
// deduplication.
if (stream.id.empty()) {
stream.id = CreateRandomString(8);
}
}
}
void AddPlanBSsrcMsidLine(uint32_t ssrc,
const StreamParams& track,
StringBuilder* os,
std::string* message) {
// draft-alvestrand-mmusic-msid-00
// a=ssrc:<ssrc-id> msid:identifier [appdata]
// The appdata consists of the "id" attribute of a MediaStreamTrack,
// which corresponds to the "id" attribute of StreamParams.
// Since a=ssrc msid signaling is used in Plan B SDP semantics, and
// multiple stream ids are not supported for Plan B, we are only adding
// a line for the first media stream id here.
// We use a special msid-id value of "-" to represent no streams,
// for Unified Plan compatibility. Plan B will always have a
// track_stream_id.
absl::string_view first_stream_id = track.first_stream_id();
absl::string_view stream_id =
first_stream_id.empty() ? kNoStreamMsid : first_stream_id;
os->Clear();
*os << "a=" << kAttributeSsrc << ":" << ssrc << " " << kSsrcAttributeMsid
<< ":" << stream_id << " " << track.id << kLineBreak;
message->append(os->str());
}
void ParsePlanBMsidSemantics(absl::string_view semantics,
SessionDescription* desc) {
if (absl::StrContainsIgnoreCase(semantics, kMediaStreamSemantic)) {
desc->set_msid_signaling(kMsidSignalingSemantic);
}
}
void SerializePlanBMsidSemantics(const SessionDescription& desc,
StringBuilder* os,
std::string* message) {
// TODO: bugs.webrtc.org/10421 - Change to & kMsidSignalingSemantic
// when we think it's safe to do so, so that we gradually fade out this old
// line that was removed from the specification.
if (desc.msid_signaling() == kMsidSignalingNotUsed) {
return;
}
os->Clear();
*os << "a=" << kAttributeMsidSemantics << ": " << kMediaStreamSemantic;
// TODO: bugs.webrtc.org/10421 - this code only looks at the first
// audio/video content. Fixing that might result in much larger SDP and the
// msid-semantic line should eventually go away so this is not worth fixing.
flat_set<std::string> media_stream_ids;
const ContentInfo* audio_content = LegacyGetFirstAudioContent(desc);
if (audio_content != nullptr) {
GetMediaStreamIds(audio_content, &media_stream_ids);
}
const ContentInfo* video_content = LegacyGetFirstVideoContent(desc);
if (video_content != nullptr) {
GetMediaStreamIds(video_content, &media_stream_ids);
}
for (const std::string& id : media_stream_ids) {
*os << " " << id;
}
*os << kLineBreak;
message->append(os->str());
}
} // namespace webrtc