description.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976
  1. /**
  2. * Copyright (c) 2019-2020 Paul-Louis Ageneau
  3. * Copyright (c) 2020 Staz Modrzynski
  4. *
  5. * This library is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU Lesser General Public
  7. * License as published by the Free Software Foundation; either
  8. * version 2.1 of the License, or (at your option) any later version.
  9. *
  10. * This library is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. * Lesser General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Lesser General Public
  16. * License along with this library; if not, write to the Free Software
  17. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  18. */
  19. #include "description.hpp"
  20. #include <algorithm>
  21. #include <array>
  22. #include <cctype>
  23. #include <chrono>
  24. #include <iostream>
  25. #include <random>
  26. #include <sstream>
  27. #include <unordered_map>
  28. using std::chrono::system_clock;
  29. namespace {
  30. using std::string;
  31. using std::string_view;
  32. inline bool match_prefix(string_view str, string_view prefix) {
  33. return str.size() >= prefix.size() &&
  34. std::mismatch(prefix.begin(), prefix.end(), str.begin()).first == prefix.end();
  35. }
  36. inline void trim_end(string &str) {
  37. str.erase(
  38. std::find_if(str.rbegin(), str.rend(), [](char c) { return !std::isspace(c); }).base(),
  39. str.end());
  40. }
  41. inline std::pair<string_view, string_view> parse_pair(string_view attr) {
  42. string_view key, value;
  43. if (size_t separator = attr.find(':'); separator != string::npos) {
  44. key = attr.substr(0, separator);
  45. value = attr.substr(separator + 1);
  46. } else {
  47. key = attr;
  48. }
  49. return std::make_pair(std::move(key), std::move(value));
  50. }
  51. template <typename T> T to_integer(string_view s) {
  52. const string str(s);
  53. try {
  54. return std::is_signed<T>::value ? T(std::stol(str)) : T(std::stoul(str));
  55. }
  56. catch(...) {
  57. throw std::invalid_argument("Invalid integer \"" + str + "\" in description");
  58. }
  59. }
  60. } // namespace
  61. namespace rtc {
  62. Description::Description(const string &sdp, Type type, Role role)
  63. : mType(Type::Unspec), mRole(role) {
  64. hintType(type);
  65. int index = -1;
  66. std::shared_ptr<Entry> current;
  67. std::istringstream ss(sdp);
  68. while (ss) {
  69. string line;
  70. std::getline(ss, line);
  71. trim_end(line);
  72. if (line.empty())
  73. continue;
  74. if (match_prefix(line, "m=")) { // Media description line (aka m-line)
  75. current = createEntry(line.substr(2), std::to_string(++index), Direction::Unknown);
  76. } else if (match_prefix(line, "o=")) { // Origin line
  77. std::istringstream origin(line.substr(2));
  78. origin >> mUsername >> mSessionId;
  79. } else if (match_prefix(line, "a=")) { // Attribute line
  80. string attr = line.substr(2);
  81. auto [key, value] = parse_pair(attr);
  82. if (key == "setup") {
  83. if (value == "active")
  84. mRole = Role::Active;
  85. else if (value == "passive")
  86. mRole = Role::Passive;
  87. else
  88. mRole = Role::ActPass;
  89. } else if (key == "fingerprint") {
  90. if (match_prefix(value, "sha-256 ")) {
  91. mFingerprint = value.substr(8);
  92. std::transform(mFingerprint->begin(), mFingerprint->end(),
  93. mFingerprint->begin(),
  94. [](char c) { return char(std::toupper(c)); });
  95. } else {
  96. PLOG_WARNING << "Unknown SDP fingerprint format: " << value;
  97. }
  98. } else if (key == "ice-ufrag") {
  99. mIceUfrag = value;
  100. } else if (key == "ice-pwd") {
  101. mIcePwd = value;
  102. } else if (key == "candidate") {
  103. addCandidate(Candidate(attr, bundleMid()));
  104. } else if (key == "end-of-candidates") {
  105. mEnded = true;
  106. } else if (current) {
  107. current->parseSdpLine(std::move(line));
  108. }
  109. } else if (current) {
  110. current->parseSdpLine(std::move(line));
  111. }
  112. }
  113. if (mUsername.empty())
  114. mUsername = "rtc";
  115. if (mSessionId.empty()) {
  116. auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
  117. std::default_random_engine generator(seed);
  118. std::uniform_int_distribution<uint32_t> uniform;
  119. mSessionId = std::to_string(uniform(generator));
  120. }
  121. }
  122. Description::Description(const string &sdp, string typeString)
  123. : Description(sdp, !typeString.empty() ? stringToType(typeString) : Type::Unspec,
  124. Role::ActPass) {}
  125. Description::Type Description::type() const { return mType; }
  126. string Description::typeString() const { return typeToString(mType); }
  127. Description::Role Description::role() const { return mRole; }
  128. string Description::bundleMid() const {
  129. // Get the mid of the first media
  130. return !mEntries.empty() ? mEntries[0]->mid() : "0";
  131. }
  132. std::optional<string> Description::iceUfrag() const { return mIceUfrag; }
  133. std::optional<string> Description::icePwd() const { return mIcePwd; }
  134. std::optional<string> Description::fingerprint() const { return mFingerprint; }
  135. bool Description::ended() const { return mEnded; }
  136. void Description::hintType(Type type) {
  137. if (mType == Type::Unspec) {
  138. mType = type;
  139. if (mType == Type::Answer && mRole == Role::ActPass)
  140. mRole = Role::Passive; // ActPass is illegal for an answer, so default to passive
  141. }
  142. }
  143. void Description::setFingerprint(string fingerprint) {
  144. mFingerprint.emplace(std::move(fingerprint));
  145. }
  146. bool Description::hasCandidate(const Candidate &candidate) const {
  147. for (const Candidate &other : mCandidates)
  148. if (candidate == other)
  149. return true;
  150. return false;
  151. }
  152. void Description::addCandidate(Candidate candidate) {
  153. candidate.hintMid(bundleMid());
  154. for (const Candidate &other : mCandidates)
  155. if (candidate == other)
  156. return;
  157. mCandidates.emplace_back(std::move(candidate));
  158. }
  159. void Description::addCandidates(std::vector<Candidate> candidates) {
  160. for (Candidate candidate : candidates)
  161. addCandidate(std::move(candidate));
  162. }
  163. void Description::endCandidates() { mEnded = true; }
  164. std::vector<Candidate> Description::extractCandidates() {
  165. std::vector<Candidate> result;
  166. std::swap(mCandidates, result);
  167. mEnded = false;
  168. return result;
  169. }
  170. Description::operator string() const { return generateSdp("\r\n"); }
  171. string Description::generateSdp(string_view eol) const {
  172. std::ostringstream sdp;
  173. // Header
  174. sdp << "v=0" << eol;
  175. sdp << "o=" << mUsername << " " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
  176. sdp << "s=-" << eol;
  177. sdp << "t=0 0" << eol;
  178. // Bundle
  179. // see Negotiating Media Multiplexing Using the Session Description Protocol
  180. // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54
  181. sdp << "a=group:BUNDLE";
  182. for (const auto &entry : mEntries)
  183. sdp << ' ' << entry->mid();
  184. sdp << eol;
  185. // Lip-sync
  186. std::ostringstream lsGroup;
  187. for (const auto &entry : mEntries)
  188. if (entry != mApplication)
  189. lsGroup << ' ' << entry->mid();
  190. if (!lsGroup.str().empty())
  191. sdp << "a=group:LS" << lsGroup.str() << eol;
  192. // Session-level attributes
  193. sdp << "a=msid-semantic:WMS *" << eol;
  194. sdp << "a=setup:" << mRole << eol;
  195. if (mIceUfrag)
  196. sdp << "a=ice-ufrag:" << *mIceUfrag << eol;
  197. if (mIcePwd)
  198. sdp << "a=ice-pwd:" << *mIcePwd << eol;
  199. if (!mEnded)
  200. sdp << "a=ice-options:trickle" << eol;
  201. if (mFingerprint)
  202. sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
  203. auto cand = defaultCandidate();
  204. const string addr = cand && cand->isResolved()
  205. ? (string(cand->family() == Candidate::Family::Ipv6 ? "IP6" : "IP4") +
  206. " " + *cand->address())
  207. : "IP4 0.0.0.0";
  208. const string port = std::to_string(
  209. cand && cand->isResolved() ? *cand->port() : 9); // Port 9 is the discard protocol
  210. // Entries
  211. bool first = true;
  212. for (const auto &entry : mEntries) {
  213. sdp << entry->generateSdp(eol, addr, port);
  214. if (std::exchange(first, false)) {
  215. // Candidates
  216. for (const auto &candidate : mCandidates)
  217. sdp << string(candidate) << eol;
  218. if (mEnded)
  219. sdp << "a=end-of-candidates" << eol;
  220. }
  221. }
  222. return sdp.str();
  223. }
  224. string Description::generateApplicationSdp(string_view eol) const {
  225. std::ostringstream sdp;
  226. // Header
  227. sdp << "v=0" << eol;
  228. sdp << "o=" << mUsername << " " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
  229. sdp << "s=-" << eol;
  230. sdp << "t=0 0" << eol;
  231. auto cand = defaultCandidate();
  232. const string addr = cand && cand->isResolved()
  233. ? (string(cand->family() == Candidate::Family::Ipv6 ? "IP6" : "IP4") +
  234. " " + *cand->address())
  235. : "IP4 0.0.0.0";
  236. const string port = std::to_string(
  237. cand && cand->isResolved() ? *cand->port() : 9); // Port 9 is the discard protocol
  238. // Application
  239. auto app = mApplication ? mApplication : std::make_shared<Application>();
  240. sdp << app->generateSdp(eol, addr, port);
  241. // Session-level attributes
  242. sdp << "a=msid-semantic:WMS *" << eol;
  243. sdp << "a=setup:" << mRole << eol;
  244. if (mIceUfrag)
  245. sdp << "a=ice-ufrag:" << *mIceUfrag << eol;
  246. if (mIcePwd)
  247. sdp << "a=ice-pwd:" << *mIcePwd << eol;
  248. if (!mEnded)
  249. sdp << "a=ice-options:trickle" << eol;
  250. if (mFingerprint)
  251. sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
  252. // Candidates
  253. for (const auto &candidate : mCandidates)
  254. sdp << string(candidate) << eol;
  255. if (mEnded)
  256. sdp << "a=end-of-candidates" << eol;
  257. return sdp.str();
  258. }
  259. std::optional<Candidate> Description::defaultCandidate() const {
  260. // Return the first host candidate with highest priority, favoring IPv4
  261. std::optional<Candidate> result;
  262. for (const auto &c : mCandidates) {
  263. if (c.type() == Candidate::Type::Host) {
  264. if (!result ||
  265. (result->family() == Candidate::Family::Ipv6 &&
  266. c.family() == Candidate::Family::Ipv4) ||
  267. (result->family() == c.family() && result->priority() < c.priority()))
  268. result.emplace(c);
  269. }
  270. }
  271. return result;
  272. }
  273. shared_ptr<Description::Entry> Description::createEntry(string mline, string mid, Direction dir) {
  274. string type = mline.substr(0, mline.find(' '));
  275. if (type == "application") {
  276. removeApplication();
  277. mApplication = std::make_shared<Application>(std::move(mid));
  278. mEntries.emplace_back(mApplication);
  279. return mApplication;
  280. } else {
  281. auto media = std::make_shared<Media>(std::move(mline), std::move(mid), dir);
  282. mEntries.emplace_back(media);
  283. return media;
  284. }
  285. }
  286. void Description::removeApplication() {
  287. if (!mApplication)
  288. return;
  289. auto it = std::find(mEntries.begin(), mEntries.end(), mApplication);
  290. if (it != mEntries.end())
  291. mEntries.erase(it);
  292. mApplication.reset();
  293. }
  294. bool Description::hasApplication() const { return mApplication != nullptr; }
  295. bool Description::hasAudioOrVideo() const {
  296. for (auto entry : mEntries)
  297. if (entry != mApplication)
  298. return true;
  299. return false;
  300. }
  301. bool Description::hasMid(string_view mid) const {
  302. for (const auto &entry : mEntries)
  303. if (entry->mid() == mid)
  304. return true;
  305. return false;
  306. }
  307. int Description::addMedia(Media media) {
  308. mEntries.emplace_back(std::make_shared<Media>(std::move(media)));
  309. return int(mEntries.size()) - 1;
  310. }
  311. int Description::addMedia(Application application) {
  312. removeApplication();
  313. mApplication = std::make_shared<Application>(std::move(application));
  314. mEntries.emplace_back(mApplication);
  315. return int(mEntries.size()) - 1;
  316. }
  317. int Description::addApplication(string mid) { return addMedia(Application(std::move(mid))); }
  318. Description::Application *Description::application() { return mApplication.get(); }
  319. int Description::addVideo(string mid, Direction dir) {
  320. return addMedia(Video(std::move(mid), dir));
  321. }
  322. int Description::addAudio(string mid, Direction dir) {
  323. return addMedia(Audio(std::move(mid), dir));
  324. }
  325. std::variant<Description::Media *, Description::Application *>
  326. Description::media(unsigned int index) {
  327. if (index >= mEntries.size())
  328. throw std::out_of_range("Media index out of range");
  329. const auto &entry = mEntries[index];
  330. if (entry == mApplication) {
  331. auto result = dynamic_cast<Application *>(entry.get());
  332. if (!result)
  333. throw std::logic_error("Bad type of application in description");
  334. return result;
  335. } else {
  336. auto result = dynamic_cast<Media *>(entry.get());
  337. if (!result)
  338. throw std::logic_error("Bad type of media in description");
  339. return result;
  340. }
  341. }
  342. std::variant<const Description::Media *, const Description::Application *>
  343. Description::media(unsigned int index) const {
  344. if (index >= mEntries.size())
  345. throw std::out_of_range("Media index out of range");
  346. const auto &entry = mEntries[index];
  347. if (entry == mApplication) {
  348. auto result = dynamic_cast<Application *>(entry.get());
  349. if (!result)
  350. throw std::logic_error("Bad type of application in description");
  351. return result;
  352. } else {
  353. auto result = dynamic_cast<Media *>(entry.get());
  354. if (!result)
  355. throw std::logic_error("Bad type of media in description");
  356. return result;
  357. }
  358. }
  359. unsigned int Description::mediaCount() const { return unsigned(mEntries.size()); }
  360. Description::Entry::Entry(const string &mline, string mid, Direction dir)
  361. : mMid(std::move(mid)), mDirection(dir) {
  362. unsigned int port;
  363. std::istringstream ss(mline);
  364. ss >> mType;
  365. ss >> port; // ignored
  366. ss >> mDescription;
  367. }
  368. void Description::Entry::setDirection(Direction dir) { mDirection = dir; }
  369. Description::Entry::operator string() const { return generateSdp("\r\n", "IP4 0.0.0.0", "9"); }
  370. string Description::Entry::generateSdp(string_view eol, string_view addr, string_view port) const {
  371. std::ostringstream sdp;
  372. sdp << "m=" << type() << ' ' << port << ' ' << description() << eol;
  373. sdp << "c=IN " << addr << eol;
  374. sdp << generateSdpLines(eol);
  375. return sdp.str();
  376. }
  377. string Description::Entry::generateSdpLines(string_view eol) const {
  378. std::ostringstream sdp;
  379. sdp << "a=bundle-only" << eol;
  380. sdp << "a=mid:" << mMid << eol;
  381. switch (mDirection) {
  382. case Direction::SendOnly:
  383. sdp << "a=sendonly" << eol;
  384. break;
  385. case Direction::RecvOnly:
  386. sdp << "a=recvonly" << eol;
  387. break;
  388. case Direction::SendRecv:
  389. sdp << "a=sendrecv" << eol;
  390. break;
  391. case Direction::Inactive:
  392. sdp << "a=inactive" << eol;
  393. break;
  394. default:
  395. // Ignore
  396. break;
  397. }
  398. for (const auto &attr : mAttributes) {
  399. if (attr.find("extmap") == string::npos && attr.find("rtcp-rsize") == string::npos)
  400. sdp << "a=" << attr << eol;
  401. }
  402. return sdp.str();
  403. }
  404. void Description::Entry::parseSdpLine(string_view line) {
  405. if (match_prefix(line, "a=")) {
  406. string_view attr = line.substr(2);
  407. auto [key, value] = parse_pair(attr);
  408. if (key == "mid")
  409. mMid = value;
  410. else if (attr == "sendonly")
  411. mDirection = Direction::SendOnly;
  412. else if (attr == "recvonly")
  413. mDirection = Direction::RecvOnly;
  414. else if (key == "sendrecv")
  415. mDirection = Direction::SendRecv;
  416. else if (key == "inactive")
  417. mDirection = Direction::Inactive;
  418. else if (key == "bundle-only") {
  419. // always added
  420. } else
  421. mAttributes.emplace_back(line.substr(2));
  422. }
  423. }
  424. std::vector<string>::iterator Description::Entry::beginAttributes() { return mAttributes.begin(); }
  425. std::vector<string>::iterator Description::Entry::endAttributes() { return mAttributes.end(); }
  426. std::vector<string>::iterator
  427. Description::Entry::removeAttribute(std::vector<string>::iterator it) {
  428. return mAttributes.erase(it);
  429. }
  430. void Description::Media::addSSRC(uint32_t ssrc, std::optional<string> name,
  431. std::optional<string> msid, std::optional<string> trackID) {
  432. if (name)
  433. mAttributes.emplace_back("ssrc:" + std::to_string(ssrc) + " cname:" + *name);
  434. else
  435. mAttributes.emplace_back("ssrc:" + std::to_string(ssrc));
  436. if (msid)
  437. mAttributes.emplace_back("ssrc:" + std::to_string(ssrc) + " msid:" + *msid + " " + trackID.value_or(*msid));
  438. mSsrcs.emplace_back(ssrc);
  439. }
  440. void Description::Media::replaceSSRC(uint32_t oldSSRC, uint32_t ssrc, std::optional<string> name,
  441. std::optional<string> msid, std::optional<string> trackID) {
  442. auto it = mAttributes.begin();
  443. while (it != mAttributes.end()) {
  444. if (it->find("ssrc:" + std::to_string(oldSSRC)) == 0) {
  445. it = mAttributes.erase(it);
  446. } else
  447. it++;
  448. }
  449. addSSRC(ssrc, std::move(name), std::move(msid), std::move(trackID));
  450. }
  451. void Description::Media::removeSSRC(uint32_t oldSSRC) {
  452. auto it = mAttributes.begin();
  453. while (it != mAttributes.end()) {
  454. if (it->find("ssrc:" + std::to_string(oldSSRC)) == 0) {
  455. it = mAttributes.erase(it);
  456. } else
  457. it++;
  458. }
  459. }
  460. bool Description::Media::hasSSRC(uint32_t ssrc) {
  461. return std::find(mSsrcs.begin(), mSsrcs.end(), ssrc) != mSsrcs.end();
  462. }
  463. Description::Application::Application(string mid)
  464. : Entry("application 9 UDP/DTLS/SCTP", std::move(mid), Direction::SendRecv) {}
  465. string Description::Application::description() const {
  466. return Entry::description() + " webrtc-datachannel";
  467. }
  468. Description::Application Description::Application::reciprocate() const {
  469. Application reciprocated(*this);
  470. reciprocated.mMaxMessageSize.reset();
  471. return reciprocated;
  472. }
  473. string Description::Application::generateSdpLines(string_view eol) const {
  474. std::ostringstream sdp;
  475. sdp << Entry::generateSdpLines(eol);
  476. if (mSctpPort)
  477. sdp << "a=sctp-port:" << *mSctpPort << eol;
  478. if (mMaxMessageSize)
  479. sdp << "a=max-message-size:" << *mMaxMessageSize << eol;
  480. return sdp.str();
  481. }
  482. void Description::Application::parseSdpLine(string_view line) {
  483. if (match_prefix(line, "a=")) {
  484. string_view attr = line.substr(2);
  485. auto [key, value] = parse_pair(attr);
  486. if (key == "sctp-port") {
  487. mSctpPort = to_integer<uint16_t>(value);
  488. } else if (key == "max-message-size") {
  489. mMaxMessageSize = to_integer<size_t>(value);
  490. } else {
  491. Entry::parseSdpLine(line);
  492. }
  493. } else {
  494. Entry::parseSdpLine(line);
  495. }
  496. }
  497. Description::Media::Media(const string &sdp) : Entry(sdp, "", Direction::Unknown) {
  498. std::istringstream ss(sdp);
  499. while (ss) {
  500. string line;
  501. std::getline(ss, line);
  502. trim_end(line);
  503. if (line.empty())
  504. continue;
  505. parseSdpLine(line);
  506. }
  507. if (mid().empty())
  508. throw std::invalid_argument("Missing mid in media SDP");
  509. }
  510. Description::Media::Media(const string &mline, string mid, Direction dir)
  511. : Entry(mline, std::move(mid), dir) {}
  512. string Description::Media::description() const {
  513. std::ostringstream desc;
  514. desc << Entry::description();
  515. for (auto it = mRtpMap.begin(); it != mRtpMap.end(); ++it)
  516. desc << ' ' << it->first;
  517. return desc.str();
  518. }
  519. Description::Media Description::Media::reciprocate() const {
  520. Media reciprocated(*this);
  521. // Invert direction
  522. switch (direction()) {
  523. case Direction::RecvOnly:
  524. reciprocated.setDirection(Direction::SendOnly);
  525. break;
  526. case Direction::SendOnly:
  527. reciprocated.setDirection(Direction::RecvOnly);
  528. break;
  529. default:
  530. // We are good
  531. break;
  532. }
  533. return reciprocated;
  534. }
  535. Description::Media::RTPMap &Description::Media::getFormat(int fmt) {
  536. auto it = mRtpMap.find(fmt);
  537. if (it != mRtpMap.end())
  538. return it->second;
  539. throw std::invalid_argument("m-line index is out of bounds");
  540. }
  541. Description::Media::RTPMap &Description::Media::getFormat(const string &fmt) {
  542. for (auto it = mRtpMap.begin(); it != mRtpMap.end(); ++it)
  543. if (it->second.format == fmt)
  544. return it->second;
  545. throw std::invalid_argument("format was not found");
  546. }
  547. void Description::Media::removeFormat(const string &fmt) {
  548. auto it = mRtpMap.begin();
  549. std::vector<int> remed;
  550. // Remove the actual formats
  551. while (it != mRtpMap.end()) {
  552. if (it->second.format == fmt) {
  553. remed.emplace_back(it->first);
  554. it = mRtpMap.erase(it);
  555. } else {
  556. it++;
  557. }
  558. }
  559. // Remove any other rtpmaps that depend on the formats we just removed
  560. it = mRtpMap.begin();
  561. while (it != mRtpMap.end()) {
  562. auto it2 = it->second.fmtps.begin();
  563. bool rem = false;
  564. while (it2 != it->second.fmtps.end()) {
  565. if (it2->find("apt=") == 0) {
  566. for (auto remid : remed) {
  567. if (it2->find(std::to_string(remid)) != string::npos) {
  568. std::cout << *it2 << ' ' << remid << std::endl;
  569. it = mRtpMap.erase(it);
  570. rem = true;
  571. break;
  572. }
  573. }
  574. break;
  575. }
  576. it2++;
  577. }
  578. if (!rem)
  579. it++;
  580. }
  581. }
  582. void Description::Video::addVideoCodec(int payloadType, string codec,
  583. std::optional<string> profile) {
  584. RTPMap map(std::to_string(payloadType) + ' ' + codec + "/90000");
  585. map.addFB("nack");
  586. map.addFB("nack pli");
  587. // map.addFB("ccm fir");
  588. map.addFB("goog-remb");
  589. if (profile)
  590. map.fmtps.emplace_back(*profile);
  591. addRTPMap(map);
  592. /* TODO
  593. * TIL that Firefox does not properly support the negotiation of RTX! It works, but doesn't
  594. * negotiate the SSRC so we have no idea what SSRC is RTX going to be. Three solutions: One) we
  595. * don't negotitate it and (maybe) break RTX support with Edge. Two) we do negotiate it and
  596. * rebuild the original packet before we send it distribute it to each track. Three) we complain
  597. * to mozilla. This one probably won't do much.
  598. */
  599. // RTX Packets
  600. // RTPMap rtx(std::to_string(payloadType+1) + " rtx/90000");
  601. // // TODO rtx-time is how long can a request be stashed for before needing to resend it.
  602. // Needs to be parameterized rtx.addAttribute("apt=" + std::to_string(payloadType) +
  603. // ";rtx-time=3000"); addRTPMap(rtx);
  604. }
  605. void Description::Audio::addAudioCodec(int payloadType, string codec,
  606. std::optional<string> profile) {
  607. // TODO This 48000/2 should be parameterized
  608. RTPMap map(std::to_string(payloadType) + ' ' + codec + "/48000/2");
  609. if (profile)
  610. map.fmtps.emplace_back(*profile);
  611. addRTPMap(map);
  612. }
  613. void Description::Media::addRTXCodec(unsigned int payloadType, unsigned int originalPayloadType,
  614. unsigned int clockRate) {
  615. RTPMap map(std::to_string(payloadType) + " RTX/" + std::to_string(clockRate));
  616. map.fmtps.emplace_back("apt=" + std::to_string(originalPayloadType));
  617. addRTPMap(map);
  618. }
  619. void Description::Video::addH264Codec(int pt, std::optional<string> profile) {
  620. addVideoCodec(pt, "H264", profile);
  621. }
  622. void Description::Video::addVP8Codec(int payloadType) {
  623. addVideoCodec(payloadType, "VP8", nullopt);
  624. }
  625. void Description::Video::addVP9Codec(int payloadType) {
  626. addVideoCodec(payloadType, "VP9", nullopt);
  627. }
  628. void Description::Media::setBitrate(int bitrate) { mBas = bitrate; }
  629. int Description::Media::getBitrate() const { return mBas; }
  630. bool Description::Media::hasPayloadType(int payloadType) const {
  631. return mRtpMap.find(payloadType) != mRtpMap.end();
  632. }
  633. string Description::Media::generateSdpLines(string_view eol) const {
  634. std::ostringstream sdp;
  635. if (mBas >= 0)
  636. sdp << "b=AS:" << mBas << eol;
  637. sdp << Entry::generateSdpLines(eol);
  638. sdp << "a=rtcp-mux" << eol;
  639. for (auto it = mRtpMap.begin(); it != mRtpMap.end(); ++it) {
  640. auto &map = it->second;
  641. // Create the a=rtpmap
  642. sdp << "a=rtpmap:" << map.pt << ' ' << map.format << '/' << map.clockRate;
  643. if (!map.encParams.empty())
  644. sdp << '/' << map.encParams;
  645. sdp << eol;
  646. for (const auto &val : map.rtcpFbs) {
  647. if (val != "transport-cc")
  648. sdp << "a=rtcp-fb:" << map.pt << ' ' << val << eol;
  649. }
  650. for (const auto &val : map.fmtps)
  651. sdp << "a=fmtp:" << map.pt << ' ' << val << eol;
  652. }
  653. return sdp.str();
  654. }
  655. void Description::Media::parseSdpLine(string_view line) {
  656. if (match_prefix(line, "a=")) {
  657. string_view attr = line.substr(2);
  658. auto [key, value] = parse_pair(attr);
  659. if (key == "rtpmap") {
  660. auto pt = Description::Media::RTPMap::parsePT(value);
  661. auto it = mRtpMap.find(pt);
  662. if (it == mRtpMap.end()) {
  663. it = mRtpMap.insert(std::make_pair(pt, Description::Media::RTPMap(value))).first;
  664. } else {
  665. it->second.setMLine(value);
  666. }
  667. } else if (key == "rtcp-fb") {
  668. size_t p = value.find(' ');
  669. int pt = to_integer<int>(value.substr(0, p));
  670. auto it = mRtpMap.find(pt);
  671. if (it == mRtpMap.end()) {
  672. it = mRtpMap.insert(std::make_pair(pt, Description::Media::RTPMap())).first;
  673. }
  674. it->second.rtcpFbs.emplace_back(value.substr(p + 1));
  675. } else if (key == "fmtp") {
  676. size_t p = value.find(' ');
  677. int pt = to_integer<int>(value.substr(0, p));
  678. auto it = mRtpMap.find(pt);
  679. if (it == mRtpMap.end())
  680. it = mRtpMap.insert(std::make_pair(pt, Description::Media::RTPMap())).first;
  681. it->second.fmtps.emplace_back(value.substr(p + 1));
  682. } else if (key == "rtcp-mux") {
  683. // always added
  684. } else if (key == "ssrc") {
  685. mSsrcs.emplace_back(to_integer<uint32_t>(value));
  686. } else {
  687. Entry::parseSdpLine(line);
  688. }
  689. } else if (match_prefix(line, "b=AS")) {
  690. mBas = to_integer<int>(line.substr(line.find(':') + 1));
  691. } else {
  692. Entry::parseSdpLine(line);
  693. }
  694. }
  695. void Description::Media::addRTPMap(const Description::Media::RTPMap &map) {
  696. mRtpMap.emplace(map.pt, map);
  697. }
  698. std::vector<uint32_t> Description::Media::getSSRCs() {
  699. std::vector<uint32_t> vec;
  700. for (auto &val : mAttributes) {
  701. PLOG_DEBUG << val;
  702. if (val.find("ssrc:") == 0) {
  703. vec.emplace_back(to_integer<uint32_t>(val.substr(5, val.find(" "))));
  704. }
  705. }
  706. return vec;
  707. }
  708. std::map<int, Description::Media::RTPMap>::iterator Description::Media::beginMaps() {
  709. return mRtpMap.begin();
  710. }
  711. std::map<int, Description::Media::RTPMap>::iterator Description::Media::endMaps() {
  712. return mRtpMap.end();
  713. }
  714. std::map<int, Description::Media::RTPMap>::iterator
  715. Description::Media::removeMap(std::map<int, Description::Media::RTPMap>::iterator iterator) {
  716. return mRtpMap.erase(iterator);
  717. }
  718. Description::Media::RTPMap::RTPMap(string_view mline) { setMLine(mline); }
  719. void Description::Media::RTPMap::removeFB(const string &str) {
  720. auto it = rtcpFbs.begin();
  721. while (it != rtcpFbs.end()) {
  722. if (it->find(str) != string::npos) {
  723. it = rtcpFbs.erase(it);
  724. } else
  725. it++;
  726. }
  727. }
  728. void Description::Media::RTPMap::addFB(const string &str) { rtcpFbs.emplace_back(str); }
  729. int Description::Media::RTPMap::parsePT(string_view view) {
  730. size_t p = view.find(' ');
  731. return to_integer<int>(view.substr(0, p));
  732. }
  733. void Description::Media::RTPMap::setMLine(string_view mline) {
  734. size_t p = mline.find(' ');
  735. this->pt = to_integer<int>(mline.substr(0, p));
  736. string_view line = mline.substr(p + 1);
  737. size_t spl = line.find('/');
  738. this->format = line.substr(0, spl);
  739. line = line.substr(spl + 1);
  740. spl = line.find('/');
  741. if (spl == string::npos) {
  742. spl = line.find(' ');
  743. }
  744. if (spl == string::npos)
  745. this->clockRate = to_integer<int>(line);
  746. else {
  747. this->clockRate = to_integer<int>(line.substr(0, spl));
  748. this->encParams = line.substr(spl + 1);
  749. }
  750. }
  751. Description::Audio::Audio(string mid, Direction dir)
  752. : Media("audio 9 UDP/TLS/RTP/SAVPF", std::move(mid), dir) {}
  753. void Description::Audio::addOpusCodec(int payloadType, std::optional<string> profile) {
  754. addAudioCodec(payloadType, "OPUS", profile);
  755. }
  756. Description::Video::Video(string mid, Direction dir)
  757. : Media("video 9 UDP/TLS/RTP/SAVPF", std::move(mid), dir) {}
  758. Description::Type Description::stringToType(const string &typeString) {
  759. using TypeMap_t = std::unordered_map<string, Type>;
  760. static const TypeMap_t TypeMap = {{"unspec", Type::Unspec},
  761. {"offer", Type::Offer},
  762. {"answer", Type::Answer},
  763. {"pranswer", Type::Pranswer},
  764. {"rollback", Type::Rollback}};
  765. auto it = TypeMap.find(typeString);
  766. return it != TypeMap.end() ? it->second : Type::Unspec;
  767. }
  768. string Description::typeToString(Type type) {
  769. switch (type) {
  770. case Type::Unspec:
  771. return "unspec";
  772. case Type::Offer:
  773. return "offer";
  774. case Type::Answer:
  775. return "answer";
  776. case Type::Pranswer:
  777. return "pranswer";
  778. case Type::Rollback:
  779. return "rollback";
  780. default:
  781. return "unknown";
  782. }
  783. }
  784. } // namespace rtc
  785. std::ostream &operator<<(std::ostream &out, const rtc::Description &description) {
  786. return out << std::string(description);
  787. }
  788. std::ostream &operator<<(std::ostream &out, rtc::Description::Type type) {
  789. return out << rtc::Description::typeToString(type);
  790. }
  791. std::ostream &operator<<(std::ostream &out, rtc::Description::Role role) {
  792. using Role = rtc::Description::Role;
  793. const char *str;
  794. // Used for SDP generation, do not change
  795. switch (role) {
  796. case Role::Active:
  797. str = "active";
  798. break;
  799. case Role::Passive:
  800. str = "passive";
  801. break;
  802. default:
  803. str = "actpass";
  804. break;
  805. }
  806. return out << str;
  807. }