mirror of
https://github.com/mii443/libdatachannel.git
synced 2025-08-22 23:25:33 +00:00
Refactored description for media handling
This commit is contained in:
2
deps/libjuice
vendored
2
deps/libjuice
vendored
Submodule deps/libjuice updated: 9be39ad50b...d8179c8300
@ -59,7 +59,7 @@ int main() {
|
|||||||
addr.sin_port = htons(5000);
|
addr.sin_port = htons(5000);
|
||||||
addr.sin_family = AF_INET;
|
addr.sin_family = AF_INET;
|
||||||
|
|
||||||
rtc::Description::VideoMedia media(rtc::Description::RecvOnly);
|
rtc::Description::Video media("video", rtc::Description::RecvOnly);
|
||||||
media.addH264Codec(96);
|
media.addH264Codec(96);
|
||||||
media.setBitrate(
|
media.setBitrate(
|
||||||
3000); // Request 3Mbps (Browsers do not encode more than 2.5MBps from a webcam)
|
3000); // Request 3Mbps (Browsers do not encode more than 2.5MBps from a webcam)
|
||||||
@ -78,7 +78,8 @@ int main() {
|
|||||||
},
|
},
|
||||||
nullptr);
|
nullptr);
|
||||||
|
|
||||||
pc->setLocalDescription();
|
// TODO
|
||||||
|
// pc->setLocalDescription();
|
||||||
|
|
||||||
std::cout << "Expect RTP video traffic on localhost:5000" << std::endl;
|
std::cout << "Expect RTP video traffic on localhost:5000" << std::endl;
|
||||||
std::cout << "Please copy/paste the answer provided by the browser: " << std::endl;
|
std::cout << "Please copy/paste the answer provided by the browser: " << std::endl;
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
document.querySelector('button').addEventListener('click', async () => {
|
document.querySelector('button').addEventListener('click', async () => {
|
||||||
let offer = JSON.parse(document.querySelector('textarea').value);
|
let offer = JSON.parse(document.querySelector('textarea').value);
|
||||||
rtc = new RTCPeerConnection({
|
rtc = new RTCPeerConnection({
|
||||||
// Requirement of libdatachannel
|
// Recommended for libdatachannel
|
||||||
bundlePolicy: "max-bundle",
|
bundlePolicy: "max-bundle",
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -36,21 +36,10 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
media.getTracks().forEach(track => rtc.addTrack(track, media));
|
media.getTracks().forEach(track => rtc.addTrack(track, media));
|
||||||
let answer= await rtc.createAnswer();
|
let answer = await rtc.createAnswer();
|
||||||
await rtc.setLocalDescription(answer);
|
await rtc.setLocalDescription(answer);
|
||||||
|
|
||||||
// For some reason, (at least) chrome requires for you to manually add the candidates.
|
|
||||||
offer.sdp.split("\n").forEach(line => {
|
|
||||||
if (line.startsWith("a=candidate")) {
|
|
||||||
let cand = line.substring(2);
|
|
||||||
rtc.addIceCandidate({
|
|
||||||
sdpMid: "video",
|
|
||||||
candidate: cand.trim()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2019 Paul-Louis Ageneau
|
* Copyright (c) 2019-2020 Paul-Louis Ageneau
|
||||||
|
* Copyright (c) 2020 Staz M
|
||||||
*
|
*
|
||||||
* This library is free software; you can redistribute it and/or
|
* This library is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
@ -24,12 +25,13 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
|
||||||
|
|
||||||
class Description {
|
class Description {
|
||||||
public:
|
public:
|
||||||
enum class Type { Unspec = 0, Offer = 1, Answer = 2 };
|
enum class Type { Unspec = 0, Offer = 1, Answer = 2 };
|
||||||
@ -44,18 +46,12 @@ public:
|
|||||||
string typeString() const;
|
string typeString() const;
|
||||||
Role role() const;
|
Role role() const;
|
||||||
string roleString() const;
|
string roleString() const;
|
||||||
string dataMid() const;
|
|
||||||
string bundleMid() const;
|
string bundleMid() const;
|
||||||
std::optional<string> fingerprint() const;
|
std::optional<string> fingerprint() const;
|
||||||
std::optional<uint16_t> sctpPort() const;
|
|
||||||
std::optional<size_t> maxMessageSize() const;
|
|
||||||
bool ended() const;
|
bool ended() const;
|
||||||
|
|
||||||
void hintType(Type type);
|
void hintType(Type type);
|
||||||
void setDataMid(string mid);
|
|
||||||
void setFingerprint(string fingerprint);
|
void setFingerprint(string fingerprint);
|
||||||
void setSctpPort(uint16_t port);
|
|
||||||
void setMaxMessageSize(size_t size);
|
|
||||||
|
|
||||||
void addCandidate(Candidate candidate);
|
void addCandidate(Candidate candidate);
|
||||||
void endCandidates();
|
void endCandidates();
|
||||||
@ -63,22 +59,68 @@ public:
|
|||||||
|
|
||||||
operator string() const;
|
operator string() const;
|
||||||
string generateSdp(string_view eol) const;
|
string generateSdp(string_view eol) const;
|
||||||
string generateDataSdp(string_view eol) const;
|
string generateApplicationSdp(string_view eol) const;
|
||||||
|
|
||||||
|
class Entry {
|
||||||
|
public:
|
||||||
|
Entry(string mline, string mid = "", Direction dir = Direction::Unknown);
|
||||||
|
virtual ~Entry() = default;
|
||||||
|
|
||||||
|
virtual string type() const { return mType; }
|
||||||
|
virtual string description() const { return mDescription; }
|
||||||
|
virtual string mid() const { return mMid; }
|
||||||
|
Direction direction() const;
|
||||||
|
|
||||||
|
virtual void parseSdpLine(string_view line);
|
||||||
|
virtual string generateSdp(string_view eol) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<string> mAttributes;
|
||||||
|
Direction mDirection;
|
||||||
|
|
||||||
|
private:
|
||||||
|
string mType;
|
||||||
|
string mDescription;
|
||||||
|
string mMid;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Application : public Entry {
|
||||||
|
public:
|
||||||
|
Application(string mid = "data");
|
||||||
|
Application(const Application &other) = default;
|
||||||
|
Application(Application &&other) = default;
|
||||||
|
|
||||||
|
string description() const override;
|
||||||
|
Application reciprocate() const;
|
||||||
|
|
||||||
|
void setSctpPort(uint16_t port) { mSctpPort = port; }
|
||||||
|
void hintSctpPort(uint16_t port) { mSctpPort = mSctpPort.value_or(port); }
|
||||||
|
void setMaxMessageSize(size_t size) { mMaxMessageSize = size; }
|
||||||
|
|
||||||
|
std::optional<uint16_t> sctpPort() const { return mSctpPort; }
|
||||||
|
std::optional<size_t> maxMessageSize() const { return mMaxMessageSize; }
|
||||||
|
|
||||||
|
virtual void parseSdpLine(string_view line) override;
|
||||||
|
virtual string generateSdp(string_view eol) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<uint16_t> mSctpPort;
|
||||||
|
std::optional<size_t> mMaxMessageSize;
|
||||||
|
};
|
||||||
|
|
||||||
// Media (non-data)
|
// Media (non-data)
|
||||||
class Media {
|
class Media : public Entry {
|
||||||
public:
|
public:
|
||||||
Media(string mline, Direction dir = Direction::Unknown, string mid = "media");
|
Media(string mline, string mid = "media", Direction dir = Direction::SendOnly);
|
||||||
|
Media(const Media &other) = default;
|
||||||
|
Media(Media &&other) = default;
|
||||||
|
virtual ~Media() = default;
|
||||||
|
|
||||||
string type() const { return mType; }
|
string description() const override;
|
||||||
string description() const { return mDescription; }
|
Media reciprocate() const;
|
||||||
string mid() const { return mMid; }
|
|
||||||
|
|
||||||
void removeFormat(const string &fmt);
|
void removeFormat(const string &fmt);
|
||||||
|
|
||||||
Direction getDirection();
|
|
||||||
void setDirection(Direction dir);
|
|
||||||
|
|
||||||
void addVideoCodec(int payloadType, const string &codec);
|
void addVideoCodec(int payloadType, const string &codec);
|
||||||
void addH264Codec(int payloadType);
|
void addH264Codec(int payloadType);
|
||||||
void addVP8Codec(int payloadType);
|
void addVP8Codec(int payloadType);
|
||||||
@ -87,19 +129,16 @@ public:
|
|||||||
void setBitrate(int bitrate);
|
void setBitrate(int bitrate);
|
||||||
int getBitrate() const;
|
int getBitrate() const;
|
||||||
|
|
||||||
void parseSdpLine(string line);
|
bool hasPayloadType(int payloadType) const;
|
||||||
string generateSdp(string_view eol) const;
|
|
||||||
|
virtual void parseSdpLine(string_view line) override;
|
||||||
|
virtual string generateSdp(string_view eol) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
string mType;
|
|
||||||
string mDescription;
|
|
||||||
string mMid;
|
|
||||||
std::vector<string> mAttributes;
|
|
||||||
std::vector<string> mAttributesl;
|
|
||||||
int mBas = -1;
|
int mBas = -1;
|
||||||
|
|
||||||
struct RTPMap {
|
struct RTPMap {
|
||||||
RTPMap(const string &mLine);
|
RTPMap(string_view mline);
|
||||||
|
|
||||||
void removeFB(const string &string);
|
void removeFB(const string &string);
|
||||||
void addFB(const string &string);
|
void addFB(const string &string);
|
||||||
@ -119,35 +158,45 @@ public:
|
|||||||
std::map<int, RTPMap> mRtpMap;
|
std::map<int, RTPMap> mRtpMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AudioMedia : public Media {
|
class Audio : public Media {
|
||||||
public:
|
public:
|
||||||
AudioMedia(Direction dir, string mid = "audio");
|
Audio(string mid = "audio", Direction dir = Direction::SendOnly);
|
||||||
};
|
};
|
||||||
|
|
||||||
class VideoMedia : public Media {
|
class Video : public Media {
|
||||||
public:
|
public:
|
||||||
VideoMedia(Direction dir, string mid = "video");
|
Video(string mid = "video", Direction dir = Direction::SendOnly);
|
||||||
};
|
};
|
||||||
|
|
||||||
bool hasMedia() const;
|
bool hasAudioOrVideo() const;
|
||||||
void addMedia(Media media);
|
|
||||||
|
int addMedia(Media media);
|
||||||
|
int addMedia(Application application);
|
||||||
|
int addApplication(string mid = "data");
|
||||||
|
int addVideo(string mid = "video", Direction dir = Direction::SendOnly);
|
||||||
|
int addAudio(string mid = "audio", Direction dir = Direction::SendOnly);
|
||||||
|
|
||||||
|
std::variant<Media *, Application *> media(int index);
|
||||||
|
std::variant<const Media *, const Application *> media(int index) const;
|
||||||
|
int mediaCount() const;
|
||||||
|
|
||||||
|
Application *application();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::shared_ptr<Entry> createEntry(string mline, string mid, Direction dir);
|
||||||
|
void removeApplication();
|
||||||
|
|
||||||
Type mType;
|
Type mType;
|
||||||
|
|
||||||
|
// Session-level attributes
|
||||||
Role mRole;
|
Role mRole;
|
||||||
string mSessionId;
|
string mSessionId;
|
||||||
string mIceUfrag, mIcePwd;
|
string mIceUfrag, mIcePwd;
|
||||||
std::optional<string> mFingerprint;
|
std::optional<string> mFingerprint;
|
||||||
|
|
||||||
struct Data {
|
// Entries
|
||||||
string mid;
|
std::vector<std::shared_ptr<Entry>> mEntries;
|
||||||
std::optional<uint16_t> sctpPort;
|
std::shared_ptr<Application> mApplication;
|
||||||
std::optional<size_t> maxMessageSize;
|
|
||||||
};
|
|
||||||
|
|
||||||
Data mData;
|
|
||||||
|
|
||||||
std::map<int, Media> mMedia; // by m-line index
|
|
||||||
|
|
||||||
// Candidates
|
// Candidates
|
||||||
std::vector<Candidate> mCandidates;
|
std::vector<Candidate> mCandidates;
|
||||||
|
@ -158,6 +158,8 @@ private:
|
|||||||
std::unordered_map<string, std::weak_ptr<Track>> mTracks; // by mid
|
std::unordered_map<string, std::weak_ptr<Track>> mTracks; // by mid
|
||||||
std::shared_mutex mDataChannelsMutex, mTracksMutex;
|
std::shared_mutex mDataChannelsMutex, mTracksMutex;
|
||||||
|
|
||||||
|
std::unordered_map<unsigned int, string> mMidFromPayloadType; // cache
|
||||||
|
|
||||||
std::atomic<State> mState;
|
std::atomic<State> mState;
|
||||||
std::atomic<GatheringState> mGatheringState;
|
std::atomic<GatheringState> mGatheringState;
|
||||||
|
|
||||||
|
@ -144,8 +144,9 @@ size_t DataChannel::maxMessageSize() const {
|
|||||||
size_t remoteMax = DEFAULT_MAX_MESSAGE_SIZE;
|
size_t remoteMax = DEFAULT_MAX_MESSAGE_SIZE;
|
||||||
if (auto pc = mPeerConnection.lock())
|
if (auto pc = mPeerConnection.lock())
|
||||||
if (auto description = pc->remoteDescription())
|
if (auto description = pc->remoteDescription())
|
||||||
if (auto maxMessageSize = description->maxMessageSize())
|
if (auto *application = description->application())
|
||||||
remoteMax = *maxMessageSize > 0 ? *maxMessageSize : LOCAL_MAX_MESSAGE_SIZE;
|
if (auto maxMessageSize = application->maxMessageSize())
|
||||||
|
remoteMax = *maxMessageSize > 0 ? *maxMessageSize : LOCAL_MAX_MESSAGE_SIZE;
|
||||||
|
|
||||||
return std::min(remoteMax, LOCAL_MAX_MESSAGE_SIZE);
|
return std::min(remoteMax, LOCAL_MAX_MESSAGE_SIZE);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2019 Paul-Louis Ageneau
|
* Copyright (c) 2019-2020 Paul-Louis Ageneau
|
||||||
|
* Copyright (c) 2020 Staz M
|
||||||
*
|
*
|
||||||
* This library is free software; you can redistribute it and/or
|
* This library is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
@ -21,16 +22,19 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
using std::shared_ptr;
|
||||||
using std::size_t;
|
using std::size_t;
|
||||||
using std::string;
|
using std::string;
|
||||||
|
using std::string_view;
|
||||||
using std::chrono::system_clock;
|
using std::chrono::system_clock;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
inline bool match_prefix(const string &str, const string &prefix) {
|
inline bool match_prefix(string_view str, string_view prefix) {
|
||||||
return str.size() >= prefix.size() &&
|
return str.size() >= prefix.size() &&
|
||||||
std::mismatch(prefix.begin(), prefix.end(), str.begin()).first == prefix.end();
|
std::mismatch(prefix.begin(), prefix.end(), str.begin()).first == prefix.end();
|
||||||
}
|
}
|
||||||
@ -41,6 +45,21 @@ inline void trim_end(string &str) {
|
|||||||
str.end());
|
str.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline std::pair<string_view, string_view> parse_pair(string_view attr) {
|
||||||
|
string_view key, value;
|
||||||
|
if (size_t separator = attr.find(':'); separator != string::npos) {
|
||||||
|
key = attr.substr(0, separator);
|
||||||
|
value = attr.substr(separator + 1);
|
||||||
|
} else {
|
||||||
|
key = attr;
|
||||||
|
}
|
||||||
|
return std::make_pair(std::move(key), std::move(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> T to_integer(string_view s) {
|
||||||
|
return std::is_signed<T>::value ? T(std::stol(string(s))) : T(std::stoul(string(s)));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
@ -52,7 +71,6 @@ Description::Description(const string &sdp, Type type) : Description(sdp, type,
|
|||||||
|
|
||||||
Description::Description(const string &sdp, Type type, Role role)
|
Description::Description(const string &sdp, Type type, Role role)
|
||||||
: mType(Type::Unspec), mRole(role) {
|
: mType(Type::Unspec), mRole(role) {
|
||||||
mData.mid = "data";
|
|
||||||
hintType(type);
|
hintType(type);
|
||||||
|
|
||||||
auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
|
auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
|
||||||
@ -61,50 +79,25 @@ Description::Description(const string &sdp, Type type, Role role)
|
|||||||
mSessionId = std::to_string(uniform(generator));
|
mSessionId = std::to_string(uniform(generator));
|
||||||
|
|
||||||
std::istringstream ss(sdp);
|
std::istringstream ss(sdp);
|
||||||
std::optional<Media> currentMedia;
|
std::shared_ptr<Entry> current;
|
||||||
|
|
||||||
int mlineIndex = 0;
|
int index = -1;
|
||||||
bool finished;
|
string line;
|
||||||
do {
|
while (std::getline(ss, line) || !line.empty()) {
|
||||||
string line;
|
|
||||||
finished = !std::getline(ss, line) && line.empty();
|
|
||||||
trim_end(line);
|
trim_end(line);
|
||||||
|
|
||||||
// Media description line (aka m-line)
|
// Media description line (aka m-line)
|
||||||
if (finished || match_prefix(line, "m=")) {
|
if (match_prefix(line, "m=")) {
|
||||||
if (currentMedia) {
|
++index;
|
||||||
if (!currentMedia->mid().empty()) {
|
string mline = line.substr(2);
|
||||||
if (currentMedia->type() == "application")
|
current = createEntry(std::move(mline), std::to_string(index), Direction::Unknown);
|
||||||
mData.mid = currentMedia->mid();
|
|
||||||
else
|
|
||||||
mMedia.emplace(mlineIndex, std::move(*currentMedia));
|
|
||||||
|
|
||||||
++mlineIndex;
|
|
||||||
|
|
||||||
} else if (line.find(" ICE/SDP") != string::npos) {
|
|
||||||
PLOG_WARNING << "SDP \"m=\" line has no corresponding mid, ignoring";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!finished)
|
|
||||||
currentMedia.emplace(Media(line.substr(2)));
|
|
||||||
|
|
||||||
// Attribute line
|
// Attribute line
|
||||||
} else if (match_prefix(line, "a=")) {
|
} else if (match_prefix(line, "a=")) {
|
||||||
string attr = line.substr(2);
|
string attr = line.substr(2);
|
||||||
|
auto [key, value] = parse_pair(attr);
|
||||||
|
|
||||||
string key, value;
|
if (key == "setup") {
|
||||||
if (size_t separator = attr.find(':'); separator != string::npos) {
|
|
||||||
key = attr.substr(0, separator);
|
|
||||||
value = attr.substr(separator + 1);
|
|
||||||
} else {
|
|
||||||
key = attr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key == "mid") {
|
|
||||||
if (currentMedia)
|
|
||||||
currentMedia->mid() = value;
|
|
||||||
|
|
||||||
} else if (key == "setup") {
|
|
||||||
if (value == "active")
|
if (value == "active")
|
||||||
mRole = Role::Active;
|
mRole = Role::Active;
|
||||||
else if (value == "passive")
|
else if (value == "passive")
|
||||||
@ -125,21 +118,18 @@ Description::Description(const string &sdp, Type type, Role role)
|
|||||||
mIceUfrag = value;
|
mIceUfrag = value;
|
||||||
} else if (key == "ice-pwd") {
|
} else if (key == "ice-pwd") {
|
||||||
mIcePwd = value;
|
mIcePwd = value;
|
||||||
} else if (key == "sctp-port") {
|
|
||||||
mData.sctpPort = uint16_t(std::stoul(value));
|
|
||||||
} else if (key == "max-message-size") {
|
|
||||||
mData.maxMessageSize = size_t(std::stoul(value));
|
|
||||||
} else if (key == "candidate") {
|
} else if (key == "candidate") {
|
||||||
addCandidate(Candidate(attr, currentMedia ? currentMedia->mid() : mData.mid));
|
addCandidate(Candidate(attr, bundleMid()));
|
||||||
} else if (key == "end-of-candidates") {
|
} else if (key == "end-of-candidates") {
|
||||||
mEnded = true;
|
mEnded = true;
|
||||||
} else if (currentMedia) {
|
} else if (current) {
|
||||||
currentMedia->parseSdpLine(std::move(line));
|
current->parseSdpLine(std::move(line));
|
||||||
}
|
}
|
||||||
} else if (currentMedia) {
|
|
||||||
currentMedia->parseSdpLine(std::move(line));
|
} else if (current) {
|
||||||
|
current->parseSdpLine(std::move(line));
|
||||||
}
|
}
|
||||||
} while (!finished);
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Description::Type Description::type() const { return mType; }
|
Description::Type Description::type() const { return mType; }
|
||||||
@ -150,22 +140,13 @@ Description::Role Description::role() const { return mRole; }
|
|||||||
|
|
||||||
string Description::roleString() const { return roleToString(mRole); }
|
string Description::roleString() const { return roleToString(mRole); }
|
||||||
|
|
||||||
string Description::dataMid() const { return mData.mid; }
|
|
||||||
|
|
||||||
string Description::bundleMid() const {
|
string Description::bundleMid() const {
|
||||||
// Get the mid of the first media
|
// Get the mid of the first media
|
||||||
if (auto it = mMedia.find(0); it != mMedia.end())
|
return !mEntries.empty() ? mEntries[0]->mid() : "0";
|
||||||
return it->second.mid();
|
|
||||||
else
|
|
||||||
return mData.mid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<string> Description::fingerprint() const { return mFingerprint; }
|
std::optional<string> Description::fingerprint() const { return mFingerprint; }
|
||||||
|
|
||||||
std::optional<uint16_t> Description::sctpPort() const { return mData.sctpPort; }
|
|
||||||
|
|
||||||
std::optional<size_t> Description::maxMessageSize() const { return mData.maxMessageSize; }
|
|
||||||
|
|
||||||
bool Description::ended() const { return mEnded; }
|
bool Description::ended() const { return mEnded; }
|
||||||
|
|
||||||
void Description::hintType(Type type) {
|
void Description::hintType(Type type) {
|
||||||
@ -176,16 +157,10 @@ void Description::hintType(Type type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Description::setDataMid(string mid) { mData.mid = mid; }
|
|
||||||
|
|
||||||
void Description::setFingerprint(string fingerprint) {
|
void Description::setFingerprint(string fingerprint) {
|
||||||
mFingerprint.emplace(std::move(fingerprint));
|
mFingerprint.emplace(std::move(fingerprint));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Description::setSctpPort(uint16_t port) { mData.sctpPort.emplace(port); }
|
|
||||||
|
|
||||||
void Description::setMaxMessageSize(size_t size) { mData.maxMessageSize.emplace(size); }
|
|
||||||
|
|
||||||
void Description::addCandidate(Candidate candidate) {
|
void Description::addCandidate(Candidate candidate) {
|
||||||
mCandidates.emplace_back(std::move(candidate));
|
mCandidates.emplace_back(std::move(candidate));
|
||||||
}
|
}
|
||||||
@ -199,10 +174,6 @@ std::vector<Candidate> Description::extractCandidates() {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Description::hasMedia() const { return !mMedia.empty(); }
|
|
||||||
|
|
||||||
void Description::addMedia(Media media) { mMedia.emplace(int(mMedia.size()), std::move(media)); }
|
|
||||||
|
|
||||||
Description::operator string() const { return generateSdp("\r\n"); }
|
Description::operator string() const { return generateSdp("\r\n"); }
|
||||||
|
|
||||||
string Description::generateSdp(string_view eol) const {
|
string Description::generateSdp(string_view eol) const {
|
||||||
@ -218,23 +189,62 @@ string Description::generateSdp(string_view eol) const {
|
|||||||
// see Negotiating Media Multiplexing Using the Session Description Protocol
|
// see Negotiating Media Multiplexing Using the Session Description Protocol
|
||||||
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54
|
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54
|
||||||
sdp << "a=group:BUNDLE";
|
sdp << "a=group:BUNDLE";
|
||||||
for (int i = 0; i < int(mMedia.size() + 1); ++i) {
|
for (const auto &entry : mEntries)
|
||||||
if (auto it = mMedia.find(i); it != mMedia.end())
|
sdp << ' ' << entry->mid();
|
||||||
sdp << ' ' << it->second.mid();
|
|
||||||
else
|
|
||||||
sdp << ' ' << mData.mid;
|
|
||||||
}
|
|
||||||
sdp << eol;
|
sdp << eol;
|
||||||
|
|
||||||
// Non-data media
|
// Lip-sync
|
||||||
if (!mMedia.empty()) {
|
std::ostringstream lsGroup;
|
||||||
// Lip-sync
|
for (const auto &entry : mEntries)
|
||||||
sdp << "a=group:LS";
|
if (entry != mApplication)
|
||||||
for (const auto &p : mMedia)
|
lsGroup << ' ' << entry->mid();
|
||||||
sdp << " " << p.second.mid();
|
|
||||||
sdp << eol;
|
if (!lsGroup.str().empty())
|
||||||
|
sdp << "a=group:LS" << lsGroup.str() << eol;
|
||||||
|
|
||||||
|
// Session-level attributes
|
||||||
|
sdp << "a=msid-semantic:WMS *" << eol;
|
||||||
|
sdp << "a=setup:" << roleToString(mRole) << eol;
|
||||||
|
sdp << "a=ice-ufrag:" << mIceUfrag << eol;
|
||||||
|
sdp << "a=ice-pwd:" << mIcePwd << eol;
|
||||||
|
|
||||||
|
if (mFingerprint)
|
||||||
|
sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
|
||||||
|
|
||||||
|
if (!mEnded)
|
||||||
|
sdp << "a=ice-options:trickle" << eol;
|
||||||
|
|
||||||
|
// Entries
|
||||||
|
bool first = true;
|
||||||
|
for (const auto &entry : mEntries) {
|
||||||
|
sdp << entry->generateSdp(eol);
|
||||||
|
|
||||||
|
if (std::exchange(first, false)) {
|
||||||
|
// Candidates
|
||||||
|
for (const auto &candidate : mCandidates)
|
||||||
|
sdp << string(candidate) << eol;
|
||||||
|
|
||||||
|
if (mEnded)
|
||||||
|
sdp << "a=end-of-candidates" << eol;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return sdp.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
string Description::generateApplicationSdp(string_view eol) const {
|
||||||
|
std::ostringstream sdp;
|
||||||
|
|
||||||
|
// Header
|
||||||
|
sdp << "v=0" << eol;
|
||||||
|
sdp << "o=- " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
|
||||||
|
sdp << "s=-" << eol;
|
||||||
|
sdp << "t=0 0" << eol;
|
||||||
|
|
||||||
|
// Application
|
||||||
|
auto app = mApplication ? mApplication : std::make_shared<Application>();
|
||||||
|
sdp << app->generateSdp(eol);
|
||||||
|
|
||||||
// Session-level attributes
|
// Session-level attributes
|
||||||
sdp << "a=msid-semantic:WMS *" << eol;
|
sdp << "a=msid-semantic:WMS *" << eol;
|
||||||
sdp << "a=setup:" << roleToString(mRole) << eol;
|
sdp << "a=setup:" << roleToString(mRole) << eol;
|
||||||
@ -247,86 +257,243 @@ string Description::generateSdp(string_view eol) const {
|
|||||||
if (mFingerprint)
|
if (mFingerprint)
|
||||||
sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
|
sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
|
||||||
|
|
||||||
// Media descriptions and attributes
|
// Candidates
|
||||||
for (int i = 0; i < int(mMedia.size() + 1); ++i) {
|
for (const auto &candidate : mCandidates)
|
||||||
if (auto it = mMedia.find(i); it != mMedia.end()) {
|
sdp << string(candidate) << eol;
|
||||||
sdp << it->second.generateSdp(eol);
|
|
||||||
} else {
|
if (mEnded)
|
||||||
// Data
|
sdp << "a=end-of-candidates" << eol;
|
||||||
const string description = "UDP/DTLS/SCTP webrtc-datachannel";
|
|
||||||
sdp << "m=application" << ' ' << (!mMedia.empty() ? 0 : 9) << ' ' << description << eol;
|
return sdp.str();
|
||||||
sdp << "c=IN IP4 0.0.0.0" << eol;
|
}
|
||||||
if (!mMedia.empty())
|
|
||||||
sdp << "a=bundle-only" << eol;
|
shared_ptr<Description::Entry> Description::createEntry(string mline, string mid, Direction dir) {
|
||||||
sdp << "a=mid:" << mData.mid << eol;
|
string type = mline.substr(0, mline.find(' '));
|
||||||
sdp << "a=sendrecv" << eol;
|
if (type == "application") {
|
||||||
if (mData.sctpPort)
|
removeApplication();
|
||||||
sdp << "a=sctp-port:" << *mData.sctpPort << eol;
|
mApplication = std::make_shared<Application>(std::move(mid));
|
||||||
if (mData.maxMessageSize)
|
mEntries.emplace_back(mApplication);
|
||||||
sdp << "a=max-message-size:" << *mData.maxMessageSize << eol;
|
return mApplication;
|
||||||
}
|
} else {
|
||||||
|
auto media = std::make_shared<Media>(std::move(mline), std::move(mid), dir);
|
||||||
|
mEntries.emplace_back(media);
|
||||||
|
return media;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Candidates
|
|
||||||
for (const auto &candidate : mCandidates)
|
|
||||||
sdp << string(candidate) << eol;
|
|
||||||
|
|
||||||
if (mEnded)
|
|
||||||
sdp << "a=end-of-candidates" << eol;
|
|
||||||
|
|
||||||
return sdp.str();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string Description::generateDataSdp(string_view eol) const {
|
void Description::removeApplication() {
|
||||||
std::ostringstream sdp;
|
if (!mApplication)
|
||||||
|
return;
|
||||||
|
|
||||||
// Header
|
auto it = std::find(mEntries.begin(), mEntries.end(), mApplication);
|
||||||
sdp << "v=0" << eol;
|
if (it != mEntries.end())
|
||||||
sdp << "o=- " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
|
mEntries.erase(it);
|
||||||
sdp << "s=-" << eol;
|
|
||||||
sdp << "t=0 0" << eol;
|
|
||||||
|
|
||||||
// Data
|
mApplication.reset();
|
||||||
sdp << "m=application 9 UDP/DTLS/SCTP webrtc-datachannel";
|
|
||||||
sdp << "c=IN IP4 0.0.0.0" << eol;
|
|
||||||
sdp << "a=mid:" << mData.mid << eol;
|
|
||||||
sdp << "a=sendrecv" << eol;
|
|
||||||
if (mData.sctpPort)
|
|
||||||
sdp << "a=sctp-port:" << *mData.sctpPort << eol;
|
|
||||||
if (mData.maxMessageSize)
|
|
||||||
sdp << "a=max-message-size:" << *mData.maxMessageSize << eol;
|
|
||||||
|
|
||||||
sdp << "a=setup:" << roleToString(mRole) << eol;
|
|
||||||
sdp << "a=ice-ufrag:" << mIceUfrag << eol;
|
|
||||||
sdp << "a=ice-pwd:" << mIcePwd << eol;
|
|
||||||
|
|
||||||
if (!mEnded)
|
|
||||||
sdp << "a=ice-options:trickle" << eol;
|
|
||||||
|
|
||||||
if (mFingerprint)
|
|
||||||
sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
|
|
||||||
|
|
||||||
// Candidates
|
|
||||||
for (const auto &candidate : mCandidates)
|
|
||||||
sdp << string(candidate) << eol;
|
|
||||||
|
|
||||||
if (mEnded)
|
|
||||||
sdp << "a=end-of-candidates" << eol;
|
|
||||||
|
|
||||||
return sdp.str();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Description::Media::Media(string mline, Direction dir, string mid) {
|
bool Description::hasAudioOrVideo() const {
|
||||||
|
for (auto entry : mEntries)
|
||||||
|
if (entry != mApplication)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Description::addMedia(Media media) {
|
||||||
|
mEntries.emplace_back(std::make_shared<Media>(std::move(media)));
|
||||||
|
return int(mEntries.size()) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Description::addMedia(Application application) {
|
||||||
|
removeApplication();
|
||||||
|
mApplication = std::make_shared<Application>(std::move(application));
|
||||||
|
mEntries.emplace_back(mApplication);
|
||||||
|
return int(mEntries.size()) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Description::addApplication(string mid) { return addMedia(Application(std::move(mid))); }
|
||||||
|
|
||||||
|
Description::Application *Description::application() { return mApplication.get(); }
|
||||||
|
|
||||||
|
int Description::addVideo(string mid, Direction dir) {
|
||||||
|
return addMedia(Video(std::move(mid), dir));
|
||||||
|
}
|
||||||
|
|
||||||
|
int Description::addAudio(string mid, Direction dir) {
|
||||||
|
return addMedia(Audio(std::move(mid), dir));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::variant<Description::Media *, Description::Application *> Description::media(int index) {
|
||||||
|
if (index < 0 || index >= int(mEntries.size()))
|
||||||
|
throw std::out_of_range("Media index out of range");
|
||||||
|
|
||||||
|
const auto &entry = mEntries[index];
|
||||||
|
if (entry == mApplication) {
|
||||||
|
auto result = dynamic_cast<Application *>(entry.get());
|
||||||
|
if (!result)
|
||||||
|
throw std::logic_error("Bad type of application in description");
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
auto result = dynamic_cast<Media *>(entry.get());
|
||||||
|
if (!result)
|
||||||
|
throw std::logic_error("Bad type of media in description");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::variant<const Description::Media *, const Description::Application *>
|
||||||
|
Description::media(int index) const {
|
||||||
|
if (index < 0 || index >= int(mEntries.size()))
|
||||||
|
throw std::out_of_range("Media index out of range");
|
||||||
|
|
||||||
|
const auto &entry = mEntries[index];
|
||||||
|
if (entry == mApplication) {
|
||||||
|
auto result = dynamic_cast<Application *>(entry.get());
|
||||||
|
if (!result)
|
||||||
|
throw std::logic_error("Bad type of application in description");
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
auto result = dynamic_cast<Media *>(entry.get());
|
||||||
|
if (!result)
|
||||||
|
throw std::logic_error("Bad type of media in description");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int Description::mediaCount() const { return int(mEntries.size()); }
|
||||||
|
|
||||||
|
Description::Entry::Entry(string mline, string mid, Direction dir)
|
||||||
|
: mDirection(dir), mMid(std::move(mid)) {
|
||||||
size_t p = mline.find(' ');
|
size_t p = mline.find(' ');
|
||||||
mType = mline.substr(0, p);
|
mType = mline.substr(0, p);
|
||||||
if (p != string::npos)
|
if (p != string::npos)
|
||||||
if (size_t q = mline.find(' ', p + 1); q != string::npos)
|
if (size_t q = mline.find(' ', p + 1); q != string::npos)
|
||||||
mDescription = mline.substr(q + 1, mline.find(' ', q + 1) - q - 2);
|
mDescription = mline.substr(q + 1, mline.find(' ', q + 1) - (q + 1));
|
||||||
|
}
|
||||||
|
|
||||||
mMid = mid;
|
string Description::Entry::generateSdp(string_view eol) const {
|
||||||
|
std::ostringstream sdp;
|
||||||
|
sdp << "m=" << type() << ' ' << 0 << ' ' << description() << eol;
|
||||||
|
sdp << "c=IN IP4 0.0.0.0" << eol;
|
||||||
|
sdp << "a=bundle-only" << eol;
|
||||||
|
sdp << "a=mid:" << mMid << eol;
|
||||||
|
|
||||||
|
switch (mDirection) {
|
||||||
|
case Direction::RecvOnly:
|
||||||
|
sdp << "a=recvonly" << eol;
|
||||||
|
break;
|
||||||
|
case Direction::SendOnly:
|
||||||
|
sdp << "a=sendonly" << eol;
|
||||||
|
break;
|
||||||
|
case Direction::SendRecv:
|
||||||
|
sdp << "a=sendrecv" << eol;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Ignore
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &attr : mAttributes)
|
||||||
|
sdp << "a=" << attr << eol;
|
||||||
|
|
||||||
|
return sdp.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Description::Entry::parseSdpLine(string_view line) {
|
||||||
|
if (match_prefix(line, "a=")) {
|
||||||
|
string_view attr = line.substr(2);
|
||||||
|
auto [key, value] = parse_pair(attr);
|
||||||
|
|
||||||
|
if (key == "mid")
|
||||||
|
mMid = value;
|
||||||
|
else if (key == "sendrecv")
|
||||||
|
mDirection = Direction::SendRecv;
|
||||||
|
else if (attr == "recvonly")
|
||||||
|
mDirection = Direction::RecvOnly;
|
||||||
|
else if (attr == "sendonly")
|
||||||
|
mDirection = Direction::SendOnly;
|
||||||
|
else
|
||||||
|
mAttributes.emplace_back(line.substr(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Description::Application::Application(string mid)
|
||||||
|
: Entry("application 9 UDP/DTLS/SCTP", std::move(mid), Direction::SendRecv) {}
|
||||||
|
|
||||||
|
string Description::Application::description() const {
|
||||||
|
return Entry::description() + " webrtc-datachannel";
|
||||||
|
}
|
||||||
|
|
||||||
|
Description::Application Description::Application::reciprocate() const {
|
||||||
|
Application reciprocated(*this);
|
||||||
|
|
||||||
|
reciprocated.mMaxMessageSize.reset();
|
||||||
|
|
||||||
|
return reciprocated;
|
||||||
|
}
|
||||||
|
|
||||||
|
string Description::Application::generateSdp(string_view eol) const {
|
||||||
|
std::ostringstream sdp;
|
||||||
|
sdp << Entry::generateSdp(eol);
|
||||||
|
|
||||||
|
if (mSctpPort)
|
||||||
|
sdp << "a=sctp-port:" << *mSctpPort << eol;
|
||||||
|
|
||||||
|
if (mMaxMessageSize)
|
||||||
|
sdp << "a=max-message-size:" << *mMaxMessageSize << eol;
|
||||||
|
|
||||||
|
return sdp.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Description::Application::parseSdpLine(string_view line) {
|
||||||
|
if (match_prefix(line, "a=")) {
|
||||||
|
string_view attr = line.substr(2);
|
||||||
|
auto [key, value] = parse_pair(attr);
|
||||||
|
|
||||||
|
if (key == "sctp-port") {
|
||||||
|
mSctpPort = to_integer<uint16_t>(value);
|
||||||
|
} else if (key == "max-message-size") {
|
||||||
|
mMaxMessageSize = to_integer<size_t>(value);
|
||||||
|
} else {
|
||||||
|
Entry::parseSdpLine(line);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Entry::parseSdpLine(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Description::Media::Media(string mline, string mid, Direction dir)
|
||||||
|
: Entry(std::move(mline), std::move(mid), dir) {
|
||||||
mAttributes.emplace_back("rtcp-mux");
|
mAttributes.emplace_back("rtcp-mux");
|
||||||
|
}
|
||||||
|
|
||||||
setDirection(dir);
|
string Description::Media::description() const {
|
||||||
|
std::ostringstream desc;
|
||||||
|
desc << Entry::description();
|
||||||
|
for (auto it = mRtpMap.begin(); it != mRtpMap.end(); ++it)
|
||||||
|
desc << ' ' << it->first;
|
||||||
|
|
||||||
|
return desc.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
Description::Media Description::Media::reciprocate() const {
|
||||||
|
Media reciprocated(*this);
|
||||||
|
|
||||||
|
// Invert direction
|
||||||
|
switch (reciprocated.mDirection) {
|
||||||
|
case Direction::RecvOnly:
|
||||||
|
reciprocated.mDirection = Direction::SendOnly;
|
||||||
|
break;
|
||||||
|
case Direction::SendOnly:
|
||||||
|
reciprocated.mDirection = Direction::RecvOnly;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// We are good
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return reciprocated;
|
||||||
}
|
}
|
||||||
|
|
||||||
Description::Media::RTPMap &Description::Media::getFormat(int fmt) {
|
Description::Media::RTPMap &Description::Media::getFormat(int fmt) {
|
||||||
@ -368,7 +535,7 @@ void Description::Media::removeFormat(const string &fmt) {
|
|||||||
if (it2->find("apt=") == 0) {
|
if (it2->find("apt=") == 0) {
|
||||||
for (auto remid : remed) {
|
for (auto remid : remed) {
|
||||||
if (it2->find(std::to_string(remid)) != string::npos) {
|
if (it2->find(std::to_string(remid)) != string::npos) {
|
||||||
std::cout << *it2 << " " << remid << std::endl;
|
std::cout << *it2 << ' ' << remid << std::endl;
|
||||||
it = mRtpMap.erase(it);
|
it = mRtpMap.erase(it);
|
||||||
rem = true;
|
rem = true;
|
||||||
break;
|
break;
|
||||||
@ -384,7 +551,7 @@ void Description::Media::removeFormat(const string &fmt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Description::Media::addVideoCodec(int payloadType, const string &codec) {
|
void Description::Media::addVideoCodec(int payloadType, const string &codec) {
|
||||||
RTPMap map(std::to_string(payloadType) + " " + codec + "/90000");
|
RTPMap map(std::to_string(payloadType) + ' ' + codec + "/90000");
|
||||||
map.addFB("nack");
|
map.addFB("nack");
|
||||||
map.addFB("goog-remb");
|
map.addFB("goog-remb");
|
||||||
mRtpMap.emplace(map.pt, map);
|
mRtpMap.emplace(map.pt, map);
|
||||||
@ -396,128 +563,82 @@ void Description::Media::addVP8Codec(int payloadType) { addVideoCodec(payloadTyp
|
|||||||
|
|
||||||
void Description::Media::addVP9Codec(int payloadType) { addVideoCodec(payloadType, "VP9"); }
|
void Description::Media::addVP9Codec(int payloadType) { addVideoCodec(payloadType, "VP9"); }
|
||||||
|
|
||||||
Description::Direction Description::Media::getDirection() {
|
|
||||||
for (auto attr : mAttributes) {
|
|
||||||
if (attr == "sendrecv")
|
|
||||||
return Direction::SendRecv;
|
|
||||||
if (attr == "recvonly")
|
|
||||||
return Direction::RecvOnly;
|
|
||||||
if (attr == "sendonly")
|
|
||||||
return Direction::SendOnly;
|
|
||||||
}
|
|
||||||
return Direction::Unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Description::Media::setBitrate(int bitrate) { mBas = bitrate; }
|
void Description::Media::setBitrate(int bitrate) { mBas = bitrate; }
|
||||||
|
|
||||||
int Description::Media::getBitrate() const { return mBas; }
|
int Description::Media::getBitrate() const { return mBas; }
|
||||||
|
|
||||||
void Description::Media::setDirection(Description::Direction dir) {
|
bool Description::Media::hasPayloadType(int payloadType) const {
|
||||||
auto it = mAttributes.begin();
|
return mRtpMap.find(payloadType) != mRtpMap.end();
|
||||||
while (it != mAttributes.end()) {
|
|
||||||
if (*it == "sendrecv" || *it == "sendonly" || *it == "recvonly")
|
|
||||||
it = mAttributes.erase(it);
|
|
||||||
else
|
|
||||||
it++;
|
|
||||||
}
|
|
||||||
if (dir == Direction::SendRecv)
|
|
||||||
mAttributes.emplace(mAttributes.begin(), "sendrecv");
|
|
||||||
else if (dir == Direction::RecvOnly)
|
|
||||||
mAttributes.emplace(mAttributes.begin(), "recvonly");
|
|
||||||
if (dir == Direction::SendOnly)
|
|
||||||
mAttributes.emplace(mAttributes.begin(), "sendonly");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string Description::Media::generateSdp(string_view eol) const {
|
string Description::Media::generateSdp(string_view eol) const {
|
||||||
std::ostringstream sdp;
|
std::ostringstream sdp;
|
||||||
|
sdp << Entry::generateSdp(eol);
|
||||||
|
|
||||||
sdp << "m=" << mType << ' ' << 0 << ' ' << mDescription;
|
if (mBas >= 0)
|
||||||
|
|
||||||
for (auto it = mRtpMap.begin(); it != mRtpMap.end(); ++it)
|
|
||||||
sdp << ' ' << it->first;
|
|
||||||
|
|
||||||
sdp << eol;
|
|
||||||
sdp << "c=IN IP4 0.0.0.0" << eol;
|
|
||||||
if (mBas > -1)
|
|
||||||
sdp << "b=AS:" << mBas << eol;
|
sdp << "b=AS:" << mBas << eol;
|
||||||
|
|
||||||
sdp << "a=bundle-only" << eol;
|
|
||||||
sdp << "a=mid:" << mMid << eol;
|
|
||||||
|
|
||||||
for (const auto &attr : mAttributes)
|
|
||||||
sdp << "a=" << attr << eol;
|
|
||||||
|
|
||||||
for (auto it = mRtpMap.begin(); it != mRtpMap.end(); ++it) {
|
for (auto it = mRtpMap.begin(); it != mRtpMap.end(); ++it) {
|
||||||
auto &map = it->second;
|
auto &map = it->second;
|
||||||
|
|
||||||
// Create the a=rtpmap
|
// Create the a=rtpmap
|
||||||
sdp << "a=rtpmap:" << map.pt << " " << map.format << "/" << map.clockRate;
|
sdp << "a=rtpmap:" << map.pt << ' ' << map.format << '/' << map.clockRate;
|
||||||
if (!map.encParams.empty())
|
if (!map.encParams.empty())
|
||||||
sdp << "/" << map.encParams;
|
sdp << '/' << map.encParams;
|
||||||
sdp << eol;
|
sdp << eol;
|
||||||
|
|
||||||
for (const auto &val : map.rtcpFbs)
|
for (const auto &val : map.rtcpFbs)
|
||||||
sdp << "a=rtcp-fb:" << map.pt << " " << val << eol;
|
sdp << "a=rtcp-fb:" << map.pt << ' ' << val << eol;
|
||||||
for (const auto &val : map.fmtps)
|
for (const auto &val : map.fmtps)
|
||||||
sdp << "a=fmtp:" << map.pt << " " << val << eol;
|
sdp << "a=fmtp:" << map.pt << ' ' << val << eol;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &attr : mAttributesl)
|
|
||||||
sdp << "a=" << attr << eol;
|
|
||||||
|
|
||||||
return sdp.str();
|
return sdp.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Description::Media::parseSdpLine(string line) {
|
void Description::Media::parseSdpLine(string_view line) {
|
||||||
if (match_prefix(line, "a=")) {
|
if (match_prefix(line, "a=")) {
|
||||||
string attr = line.substr(2);
|
string_view attr = line.substr(2);
|
||||||
|
auto [key, value] = parse_pair(attr);
|
||||||
|
|
||||||
string key, value;
|
if (key == "rtpmap") {
|
||||||
if (size_t separator = attr.find(':'); separator != string::npos) {
|
|
||||||
key = attr.substr(0, separator);
|
|
||||||
value = attr.substr(separator + 1);
|
|
||||||
} else {
|
|
||||||
key = attr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key == "mid") {
|
|
||||||
mMid = value;
|
|
||||||
} else if (key == "rtpmap") {
|
|
||||||
Description::Media::RTPMap map(value);
|
Description::Media::RTPMap map(value);
|
||||||
mRtpMap.emplace(map.pt, map);
|
int pt = map.pt;
|
||||||
|
mRtpMap.emplace(pt, std::move(map));
|
||||||
} else if (key == "rtcp-fb") {
|
} else if (key == "rtcp-fb") {
|
||||||
size_t p = value.find(' ');
|
size_t p = value.find(' ');
|
||||||
int pt = std::stoi(value.substr(0, p));
|
int pt = to_integer<int>(value.substr(0, p));
|
||||||
auto it = mRtpMap.find(pt);
|
auto it = mRtpMap.find(pt);
|
||||||
if (it == mRtpMap.end()) {
|
if (it == mRtpMap.end()) {
|
||||||
PLOG_WARNING << "rtcp-fb applied before it's rtpmap. Ignoring";
|
PLOG_WARNING << "rtcp-fb applied before its rtpmap. Ignoring";
|
||||||
} else
|
} else {
|
||||||
it->second.rtcpFbs.emplace_back(value.substr(p + 1));
|
it->second.rtcpFbs.emplace_back(value.substr(p + 1));
|
||||||
|
}
|
||||||
} else if (key == "fmtp") {
|
} else if (key == "fmtp") {
|
||||||
size_t p = value.find(' ');
|
size_t p = value.find(' ');
|
||||||
int pt = std::stoi(value.substr(0, p));
|
int pt = to_integer<int>(value.substr(0, p));
|
||||||
auto it = mRtpMap.find(pt);
|
auto it = mRtpMap.find(pt);
|
||||||
if (it == mRtpMap.end()) {
|
if (it == mRtpMap.end()) {
|
||||||
PLOG_WARNING << "fmtp applied before it's rtpmap. Ignoring";
|
PLOG_WARNING << "fmtp applied before its rtpmap. Ignoring";
|
||||||
} else {
|
} else {
|
||||||
it->second.fmtps.emplace_back(value.substr(p + 1));
|
it->second.fmtps.emplace_back(value.substr(p + 1));
|
||||||
}
|
}
|
||||||
} else if (key == "b") {
|
|
||||||
// TODO
|
|
||||||
} else {
|
} else {
|
||||||
mAttributes.emplace_back(line.substr(2));
|
Entry::parseSdpLine(line);
|
||||||
}
|
}
|
||||||
} else if (match_prefix(line, "b=AS")) {
|
} else if (match_prefix(line, "b=AS")) {
|
||||||
mBas = std::stoi(line.substr(line.find(':') + 1));
|
mBas = to_integer<int>(line.substr(line.find(':') + 1));
|
||||||
|
} else {
|
||||||
|
Entry::parseSdpLine(line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Description::Media::RTPMap::RTPMap(const string &mline) {
|
Description::Media::RTPMap::RTPMap(string_view mline) {
|
||||||
size_t p = mline.find(' ');
|
size_t p = mline.find(' ');
|
||||||
|
|
||||||
this->pt = std::stoi(mline.substr(0, p));
|
this->pt = to_integer<int>(mline.substr(0, p));
|
||||||
|
|
||||||
auto line = mline.substr(p + 1);
|
string_view line = mline.substr(p + 1);
|
||||||
size_t spl = line.find('/');
|
size_t spl = line.find('/');
|
||||||
this->format = line.substr(0, spl);
|
this->format = line.substr(0, spl);
|
||||||
|
|
||||||
@ -527,14 +648,14 @@ Description::Media::RTPMap::RTPMap(const string &mline) {
|
|||||||
spl = line.find(' ');
|
spl = line.find(' ');
|
||||||
}
|
}
|
||||||
if (spl == string::npos)
|
if (spl == string::npos)
|
||||||
this->clockRate = std::stoi(line);
|
this->clockRate = to_integer<int>(line);
|
||||||
else {
|
else {
|
||||||
this->clockRate = std::stoi(line.substr(0, spl));
|
this->clockRate = to_integer<int>(line.substr(0, spl));
|
||||||
this->encParams = line.substr(spl);
|
this->encParams = line.substr(spl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Description::Media::RTPMap::removeFB(const string& string) {
|
void Description::Media::RTPMap::removeFB(const string &string) {
|
||||||
auto it = rtcpFbs.begin();
|
auto it = rtcpFbs.begin();
|
||||||
while (it != rtcpFbs.end()) {
|
while (it != rtcpFbs.end()) {
|
||||||
if (it->find(string) != std::string::npos) {
|
if (it->find(string) != std::string::npos) {
|
||||||
@ -546,12 +667,11 @@ void Description::Media::RTPMap::removeFB(const string& string) {
|
|||||||
|
|
||||||
void Description::Media::RTPMap::addFB(const string &string) { rtcpFbs.emplace_back(string); }
|
void Description::Media::RTPMap::addFB(const string &string) { rtcpFbs.emplace_back(string); }
|
||||||
|
|
||||||
Description::AudioMedia::AudioMedia(Direction dir, string mid)
|
Description::Audio::Audio(string mid, Direction dir)
|
||||||
: Media("audio 9 UDP/TLS/RTP/SAVPF", dir, std::move(mid)) {
|
: Media("audio 9 UDP/TLS/RTP/SAVPF", std::move(mid), dir) {}
|
||||||
}
|
|
||||||
|
|
||||||
Description::VideoMedia::VideoMedia(Direction dir, string mid)
|
Description::Video::Video(string mid, Direction dir)
|
||||||
: Media("video 9 UDP/TLS/RTP/SAVPF", dir, std::move(mid)) {}
|
: Media("video 9 UDP/TLS/RTP/SAVPF", std::move(mid), dir) {}
|
||||||
|
|
||||||
Description::Type Description::stringToType(const string &typeString) {
|
Description::Type Description::stringToType(const string &typeString) {
|
||||||
if (typeString == "offer")
|
if (typeString == "offer")
|
||||||
@ -589,4 +709,3 @@ string Description::roleToString(Role role) {
|
|||||||
std::ostream &operator<<(std::ostream &out, const rtc::Description &description) {
|
std::ostream &operator<<(std::ostream &out, const rtc::Description &description) {
|
||||||
return out << std::string(description);
|
return out << std::string(description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +124,8 @@ void IceTransport::setRemoteDescription(const Description &description) {
|
|||||||
mRole = description.role() == Description::Role::Active ? Description::Role::Passive
|
mRole = description.role() == Description::Role::Active ? Description::Role::Passive
|
||||||
: Description::Role::Active;
|
: Description::Role::Active;
|
||||||
mMid = description.bundleMid();
|
mMid = description.bundleMid();
|
||||||
if (juice_set_remote_description(mAgent.get(), description.generateDataSdp("\r\n").c_str()) < 0)
|
if (juice_set_remote_description(mAgent.get(),
|
||||||
|
description.generateApplicationSdp("\r\n").c_str()) < 0)
|
||||||
throw std::runtime_error("Failed to parse ICE settings from remote SDP");
|
throw std::runtime_error("Failed to parse ICE settings from remote SDP");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -487,8 +488,8 @@ void IceTransport::setRemoteDescription(const Description &description) {
|
|||||||
mTrickleTimeout = !description.ended() ? 30s : 0s;
|
mTrickleTimeout = !description.ended() ? 30s : 0s;
|
||||||
|
|
||||||
// Warning: libnice expects "\n" as end of line
|
// Warning: libnice expects "\n" as end of line
|
||||||
if (nice_agent_parse_remote_sdp(mNiceAgent.get(), description.generateDataSdp("\n").c_str()) <
|
if (nice_agent_parse_remote_sdp(mNiceAgent.get(),
|
||||||
0)
|
description.generateApplicationSdp("\n").c_str()) < 0)
|
||||||
throw std::runtime_error("Failed to parse ICE settings from remote SDP");
|
throw std::runtime_error("Failed to parse ICE settings from remote SDP");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +99,9 @@ void PeerConnection::setLocalDescription() {
|
|||||||
void PeerConnection::setRemoteDescription(Description description) {
|
void PeerConnection::setRemoteDescription(Description description) {
|
||||||
PLOG_VERBOSE << "Setting remote description: " << string(description);
|
PLOG_VERBOSE << "Setting remote description: " << string(description);
|
||||||
|
|
||||||
|
if (description.mediaCount() == 0)
|
||||||
|
throw std::runtime_error("Remote description has no media line");
|
||||||
|
|
||||||
if (!description.fingerprint())
|
if (!description.fingerprint())
|
||||||
throw std::runtime_error("Remote description is incomplete");
|
throw std::runtime_error("Remote description is incomplete");
|
||||||
|
|
||||||
@ -231,7 +234,7 @@ void PeerConnection::onGatheringStateChange(std::function<void(GatheringState st
|
|||||||
bool PeerConnection::hasMedia() const {
|
bool PeerConnection::hasMedia() const {
|
||||||
auto local = localDescription();
|
auto local = localDescription();
|
||||||
auto remote = remoteDescription();
|
auto remote = remoteDescription();
|
||||||
return (local && local->hasMedia()) || (remote && remote->hasMedia());
|
return (local && local->hasAudioOrVideo()) || (remote && remote->hasAudioOrVideo());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Track> PeerConnection::createTrack(Description::Media description) {
|
std::shared_ptr<Track> PeerConnection::createTrack(Description::Media description) {
|
||||||
@ -380,7 +383,11 @@ shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
|
|||||||
if (auto transport = std::atomic_load(&mSctpTransport))
|
if (auto transport = std::atomic_load(&mSctpTransport))
|
||||||
return transport;
|
return transport;
|
||||||
|
|
||||||
uint16_t sctpPort = remoteDescription()->sctpPort().value_or(DEFAULT_SCTP_PORT);
|
auto remote = remoteDescription();
|
||||||
|
if (!remote || !remote->application())
|
||||||
|
throw std::logic_error("Initializing SCTP transport without application description");
|
||||||
|
|
||||||
|
uint16_t sctpPort = remote->application()->sctpPort().value_or(DEFAULT_SCTP_PORT);
|
||||||
auto lower = std::atomic_load(&mDtlsTransport);
|
auto lower = std::atomic_load(&mDtlsTransport);
|
||||||
auto transport = std::make_shared<SctpTransport>(
|
auto transport = std::make_shared<SctpTransport>(
|
||||||
lower, sctpPort, weak_bind(&PeerConnection::forwardMessage, this, _1),
|
lower, sctpPort, weak_bind(&PeerConnection::forwardMessage, this, _1),
|
||||||
@ -512,11 +519,41 @@ void PeerConnection::forwardMedia(message_ptr message) {
|
|||||||
if (!message)
|
if (!message)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
string mid;
|
unsigned int payloadType = message->stream;
|
||||||
// TODO: stream (PT) to mid
|
std::optional<string> mid;
|
||||||
|
if (auto it = mMidFromPayloadType.find(payloadType); it != mMidFromPayloadType.end()) {
|
||||||
|
mid = it->second;
|
||||||
|
} else {
|
||||||
|
std::lock_guard lock(mLocalDescriptionMutex);
|
||||||
|
if (!mLocalDescription)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int i = 0; i < mLocalDescription->mediaCount(); ++i) {
|
||||||
|
if (auto found = std::visit( // reciprocate each media
|
||||||
|
rtc::overloaded{[&](Description::Application *) -> std::optional<string> {
|
||||||
|
return std::nullopt;
|
||||||
|
},
|
||||||
|
[&](Description::Media *media) -> std::optional<string> {
|
||||||
|
return media->hasPayloadType(payloadType)
|
||||||
|
? std::make_optional(media->mid())
|
||||||
|
: nullopt;
|
||||||
|
}},
|
||||||
|
mLocalDescription->media(i))) {
|
||||||
|
|
||||||
|
mMidFromPayloadType.emplace(payloadType, *found);
|
||||||
|
mid = *found;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mid) {
|
||||||
|
PLOG_WARNING << "Track not found for payload type " << payloadType;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_lock lock(mTracksMutex); // read-only
|
std::shared_lock lock(mTracksMutex); // read-only
|
||||||
if (auto it = mTracks.find(mid); it != mTracks.end())
|
if (auto it = mTracks.find(*mid); it != mTracks.end())
|
||||||
if (auto track = it->second.lock())
|
if (auto track = it->second.lock())
|
||||||
track->incoming(message);
|
track->incoming(message);
|
||||||
}
|
}
|
||||||
@ -613,25 +650,56 @@ void PeerConnection::remoteCloseDataChannels() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PeerConnection::processLocalDescription(Description description) {
|
void PeerConnection::processLocalDescription(Description description) {
|
||||||
std::optional<uint16_t> remoteSctpPort;
|
|
||||||
std::optional<string> remoteDataMid;
|
|
||||||
if (auto remote = remoteDescription()) {
|
if (auto remote = remoteDescription()) {
|
||||||
remoteDataMid = remote->dataMid();
|
// Reciprocate remote description
|
||||||
remoteSctpPort = remote->sctpPort();
|
for (int i = 0; i < remote->mediaCount(); ++i)
|
||||||
|
std::visit( // reciprocate each media
|
||||||
|
rtc::overloaded{
|
||||||
|
[&](Description::Application *app) {
|
||||||
|
PLOG_DEBUG << "Reciprocating application in local description, mid=\""
|
||||||
|
<< app->mid() << "\"";
|
||||||
|
auto reciprocated = app->reciprocate();
|
||||||
|
reciprocated.hintSctpPort(DEFAULT_SCTP_PORT);
|
||||||
|
reciprocated.setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
|
||||||
|
description.addMedia(std::move(reciprocated));
|
||||||
|
},
|
||||||
|
[&](Description::Media *media) {
|
||||||
|
PLOG_DEBUG << "Reciprocating media in local description, mid=\""
|
||||||
|
<< media->mid() << "\"";
|
||||||
|
|
||||||
|
description.addMedia(media->reciprocate());
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remote->media(i));
|
||||||
|
} else {
|
||||||
|
// Add application for data channels
|
||||||
|
{
|
||||||
|
std::shared_lock lock(mDataChannelsMutex);
|
||||||
|
if (!mDataChannels.empty()) {
|
||||||
|
const string mid = "data";
|
||||||
|
PLOG_DEBUG << "Adding application to local description, mid=\"" << mid << "\"";
|
||||||
|
Description::Application app;
|
||||||
|
app.setSctpPort(DEFAULT_SCTP_PORT);
|
||||||
|
app.setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
|
||||||
|
description.addMedia(std::move(app));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add media for local tracks
|
||||||
|
{
|
||||||
|
std::shared_lock lock(mTracksMutex);
|
||||||
|
for (auto it = mTracks.begin(); it != mTracks.end(); ++it) {
|
||||||
|
if (auto track = it->second.lock()) {
|
||||||
|
PLOG_DEBUG << "Adding media to local description, mid=\"" << track->mid()
|
||||||
|
<< "\"";
|
||||||
|
description.addMedia(track->description());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the same data mid as remote
|
// Set local fingerprint (wait for certificate if necessary)
|
||||||
if (remoteDataMid)
|
description.setFingerprint(mCertificate.get()->fingerprint());
|
||||||
description.setDataMid(*remoteDataMid);
|
|
||||||
|
|
||||||
// Set the media
|
|
||||||
for (auto it = mTracks.begin(); it != mTracks.end(); ++it)
|
|
||||||
if (auto track = it->second.lock())
|
|
||||||
description.addMedia(track->description());
|
|
||||||
|
|
||||||
description.setSctpPort(remoteSctpPort.value_or(DEFAULT_SCTP_PORT));
|
|
||||||
description.setMaxMessageSize(LOCAL_MAX_MESSAGE_SIZE);
|
|
||||||
description.setFingerprint(mCertificate.get()->fingerprint()); // wait for certificate
|
|
||||||
|
|
||||||
std::lock_guard lock(mLocalDescriptionMutex);
|
std::lock_guard lock(mLocalDescriptionMutex);
|
||||||
mLocalDescription.emplace(std::move(description));
|
mLocalDescription.emplace(std::move(description));
|
||||||
|
@ -52,7 +52,7 @@ void test_connectivity() {
|
|||||||
if (!pc2)
|
if (!pc2)
|
||||||
return;
|
return;
|
||||||
cout << "Description 1: " << sdp << endl;
|
cout << "Description 1: " << sdp << endl;
|
||||||
pc2->setRemoteDescription(std::move(sdp));
|
pc2->setRemoteDescription(string(sdp));
|
||||||
});
|
});
|
||||||
|
|
||||||
pc1->onLocalCandidate([wpc2 = make_weak_ptr(pc2)](Candidate candidate) {
|
pc1->onLocalCandidate([wpc2 = make_weak_ptr(pc2)](Candidate candidate) {
|
||||||
@ -60,7 +60,7 @@ void test_connectivity() {
|
|||||||
if (!pc2)
|
if (!pc2)
|
||||||
return;
|
return;
|
||||||
cout << "Candidate 1: " << candidate << endl;
|
cout << "Candidate 1: " << candidate << endl;
|
||||||
pc2->addRemoteCandidate(std::move(candidate));
|
pc2->addRemoteCandidate(string(candidate));
|
||||||
});
|
});
|
||||||
|
|
||||||
pc1->onStateChange([](PeerConnection::State state) { cout << "State 1: " << state << endl; });
|
pc1->onStateChange([](PeerConnection::State state) { cout << "State 1: " << state << endl; });
|
||||||
@ -74,7 +74,7 @@ void test_connectivity() {
|
|||||||
if (!pc1)
|
if (!pc1)
|
||||||
return;
|
return;
|
||||||
cout << "Description 2: " << sdp << endl;
|
cout << "Description 2: " << sdp << endl;
|
||||||
pc1->setRemoteDescription(std::move(sdp));
|
pc1->setRemoteDescription(string(sdp));
|
||||||
});
|
});
|
||||||
|
|
||||||
pc2->onLocalCandidate([wpc1 = make_weak_ptr(pc1)](Candidate candidate) {
|
pc2->onLocalCandidate([wpc1 = make_weak_ptr(pc1)](Candidate candidate) {
|
||||||
@ -82,7 +82,7 @@ void test_connectivity() {
|
|||||||
if (!pc1)
|
if (!pc1)
|
||||||
return;
|
return;
|
||||||
cout << "Candidate 2: " << candidate << endl;
|
cout << "Candidate 2: " << candidate << endl;
|
||||||
pc1->addRemoteCandidate(std::move(candidate));
|
pc1->addRemoteCandidate(string(candidate));
|
||||||
});
|
});
|
||||||
|
|
||||||
pc2->onStateChange([](PeerConnection::State state) { cout << "State 2: " << state << endl; });
|
pc2->onStateChange([](PeerConnection::State state) { cout << "State 2: " << state << endl; });
|
||||||
|
Reference in New Issue
Block a user