/** * Copyright (c) 2019-2020 Paul-Louis Ageneau * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "rtc.h" #include "rtc.hpp" #include "impl/internals.hpp" #include #include #include #include #include #include using namespace rtc; using std::chrono::milliseconds; namespace { std::unordered_map> peerConnectionMap; std::unordered_map> dataChannelMap; std::unordered_map> trackMap; #if RTC_ENABLE_MEDIA std::unordered_map> rtcpChainableHandlerMap; std::unordered_map> rtcpSrReporterMap; std::unordered_map> rtpConfigMap; #endif #if RTC_ENABLE_WEBSOCKET std::unordered_map> webSocketMap; #endif std::unordered_map userPointerMap; std::mutex mutex; int lastId = 0; optional getUserPointer(int id) { std::lock_guard lock(mutex); auto it = userPointerMap.find(id); return it != userPointerMap.end() ? std::make_optional(it->second) : nullopt; } void setUserPointer(int i, void *ptr) { std::lock_guard lock(mutex); userPointerMap[i] = ptr; } shared_ptr getPeerConnection(int id) { std::lock_guard lock(mutex); if (auto it = peerConnectionMap.find(id); it != peerConnectionMap.end()) return it->second; else throw std::invalid_argument("PeerConnection ID does not exist"); } shared_ptr getDataChannel(int id) { std::lock_guard lock(mutex); if (auto it = dataChannelMap.find(id); it != dataChannelMap.end()) return it->second; else throw std::invalid_argument("DataChannel ID does not exist"); } shared_ptr getTrack(int id) { std::lock_guard lock(mutex); if (auto it = trackMap.find(id); it != trackMap.end()) return it->second; else throw std::invalid_argument("Track ID does not exist"); } int emplacePeerConnection(shared_ptr ptr) { std::lock_guard lock(mutex); int pc = ++lastId; peerConnectionMap.emplace(std::make_pair(pc, ptr)); userPointerMap.emplace(std::make_pair(pc, nullptr)); return pc; } int emplaceDataChannel(shared_ptr ptr) { std::lock_guard lock(mutex); int dc = ++lastId; dataChannelMap.emplace(std::make_pair(dc, ptr)); userPointerMap.emplace(std::make_pair(dc, nullptr)); return dc; } int emplaceTrack(shared_ptr ptr) { std::lock_guard lock(mutex); int tr = ++lastId; trackMap.emplace(std::make_pair(tr, ptr)); userPointerMap.emplace(std::make_pair(tr, nullptr)); return tr; } void erasePeerConnection(int pc) { std::lock_guard lock(mutex); if (peerConnectionMap.erase(pc) == 0) throw std::invalid_argument("Peer Connection ID does not exist"); userPointerMap.erase(pc); } void eraseDataChannel(int dc) { std::lock_guard lock(mutex); if (dataChannelMap.erase(dc) == 0) throw std::invalid_argument("Data Channel ID does not exist"); userPointerMap.erase(dc); } void eraseTrack(int tr) { std::lock_guard lock(mutex); if (trackMap.erase(tr) == 0) throw std::invalid_argument("Track ID does not exist"); #if RTC_ENABLE_MEDIA rtcpSrReporterMap.erase(tr); rtcpChainableHandlerMap.erase(tr); rtpConfigMap.erase(tr); #endif userPointerMap.erase(tr); } #if RTC_ENABLE_MEDIA shared_ptr getRtcpSrReporter(int id) { std::lock_guard lock(mutex); if (auto it = rtcpSrReporterMap.find(id); it != rtcpSrReporterMap.end()) { return it->second; } else { throw std::invalid_argument("RTCP SR reporter ID does not exist"); } } void emplaceRtcpSrReporter(shared_ptr ptr, int tr) { std::lock_guard lock(mutex); rtcpSrReporterMap.emplace(std::make_pair(tr, ptr)); } shared_ptr getMediaChainableHandler(int id) { std::lock_guard lock(mutex); if (auto it = rtcpChainableHandlerMap.find(id); it != rtcpChainableHandlerMap.end()) { return it->second; } else { throw std::invalid_argument("RTCP chainable handler ID does not exist"); } } void emplaceMediaChainableHandler(shared_ptr ptr, int tr) { std::lock_guard lock(mutex); rtcpChainableHandlerMap.emplace(std::make_pair(tr, ptr)); } shared_ptr getRtpConfig(int id) { std::lock_guard lock(mutex); if (auto it = rtpConfigMap.find(id); it != rtpConfigMap.end()) { return it->second; } else { throw std::invalid_argument("RTP configuration ID does not exist"); } } void emplaceRtpConfig(shared_ptr ptr, int tr) { std::lock_guard lock(mutex); rtpConfigMap.emplace(std::make_pair(tr, ptr)); } shared_ptr createRtpPacketizationConfig(const rtcPacketizationHandlerInit *init) { if (!init) throw std::invalid_argument("Unexpected null pointer for packetization handler init"); if (!init->cname) throw std::invalid_argument("Unexpected null pointer for cname"); return std::make_shared(init->ssrc, init->cname, init->payloadType, init->clockRate, init->sequenceNumber, init->timestamp); } #endif // RTC_ENABLE_MEDIA #if RTC_ENABLE_WEBSOCKET shared_ptr getWebSocket(int id) { std::lock_guard lock(mutex); if (auto it = webSocketMap.find(id); it != webSocketMap.end()) return it->second; else throw std::invalid_argument("WebSocket ID does not exist"); } int emplaceWebSocket(shared_ptr ptr) { std::lock_guard lock(mutex); int ws = ++lastId; webSocketMap.emplace(std::make_pair(ws, ptr)); userPointerMap.emplace(std::make_pair(ws, nullptr)); return ws; } void eraseWebSocket(int ws) { std::lock_guard lock(mutex); if (webSocketMap.erase(ws) == 0) throw std::invalid_argument("WebSocket ID does not exist"); userPointerMap.erase(ws); } #endif shared_ptr getChannel(int id) { std::lock_guard lock(mutex); if (auto it = dataChannelMap.find(id); it != dataChannelMap.end()) return it->second; if (auto it = trackMap.find(id); it != trackMap.end()) return it->second; #if RTC_ENABLE_WEBSOCKET if (auto it = webSocketMap.find(id); it != webSocketMap.end()) return it->second; #endif throw std::invalid_argument("DataChannel, Track, or WebSocket ID does not exist"); } template int wrap(F func) { try { return int(func()); } catch (const std::invalid_argument &e) { PLOG_ERROR << e.what(); return RTC_ERR_INVALID; } catch (const std::exception &e) { PLOG_ERROR << e.what(); return RTC_ERR_FAILURE; } } int copyAndReturn(string s, char *buffer, int size) { if (!buffer) return int(s.size() + 1); if (size < int(s.size())) return RTC_ERR_TOO_SMALL; std::copy(s.begin(), s.end(), buffer); buffer[s.size()] = '\0'; return int(s.size() + 1); } int copyAndReturn(binary b, char *buffer, int size) { if (!buffer) return int(b.size()); if (size < int(b.size())) return RTC_ERR_TOO_SMALL; auto data = reinterpret_cast(b.data()); std::copy(data, data + b.size(), buffer); buffer[b.size()] = '\0'; return int(b.size()); } template int copyAndReturn(std::vector b, T *buffer, int size) { if (!buffer) return int(b.size()); if (size < int(b.size())) return RTC_ERR_TOO_SMALL; memcpy(buffer, b.data(), size * sizeof(*buffer)); return int(b.size()); } string lowercased(string str) { std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { return std::tolower(c); }); return str; } } // namespace void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb) { LogCallback callback = nullptr; if (cb) callback = [cb](LogLevel level, string message) { cb(static_cast(level), message.c_str()); }; InitLogger(static_cast(level), callback); } void rtcSetUserPointer(int i, void *ptr) { setUserPointer(i, ptr); } void *rtcGetUserPointer(int i) { return getUserPointer(i).value_or(nullptr); } int rtcCreatePeerConnection(const rtcConfiguration *config) { return wrap([config] { Configuration c; for (int i = 0; i < config->iceServersCount; ++i) c.iceServers.emplace_back(string(config->iceServers[i])); if (config->bindAddress) c.bindAddress = string(config->bindAddress); if (config->portRangeBegin > 0 || config->portRangeEnd > 0) { c.portRangeBegin = config->portRangeBegin; c.portRangeEnd = config->portRangeEnd; } c.certificateType = static_cast(config->certificateType); c.enableIceTcp = config->enableIceTcp; c.disableAutoNegotiation = config->disableAutoNegotiation; if (config->mtu > 0) c.mtu = size_t(config->mtu); if (config->maxMessageSize) c.maxMessageSize = size_t(config->maxMessageSize); return emplacePeerConnection(std::make_shared(c)); }); } int rtcDeletePeerConnection(int pc) { return wrap([pc] { auto peerConnection = getPeerConnection(pc); peerConnection->onDataChannel(nullptr); peerConnection->onTrack(nullptr); peerConnection->onLocalDescription(nullptr); peerConnection->onLocalCandidate(nullptr); peerConnection->onStateChange(nullptr); peerConnection->onGatheringStateChange(nullptr); erasePeerConnection(pc); return RTC_ERR_SUCCESS; }); } int rtcCreateDataChannel(int pc, const char *label) { return rtcCreateDataChannelEx(pc, label, nullptr); } int rtcCreateDataChannelEx(int pc, const char *label, const rtcDataChannelInit *init) { return wrap([&] { DataChannelInit dci = {}; if (init) { auto *reliability = &init->reliability; dci.reliability.unordered = reliability->unordered; if (reliability->unreliable) { if (reliability->maxPacketLifeTime > 0) { dci.reliability.type = Reliability::Type::Timed; dci.reliability.rexmit = milliseconds(reliability->maxPacketLifeTime); } else { dci.reliability.type = Reliability::Type::Rexmit; dci.reliability.rexmit = reliability->maxRetransmits; } } else { dci.reliability.type = Reliability::Type::Reliable; } dci.negotiated = init->negotiated; dci.id = init->manualStream ? std::make_optional(init->stream) : nullopt; dci.protocol = init->protocol ? init->protocol : ""; } auto peerConnection = getPeerConnection(pc); int dc = emplaceDataChannel( peerConnection->createDataChannel(string(label ? label : ""), std::move(dci))); if (auto ptr = getUserPointer(pc)) rtcSetUserPointer(dc, *ptr); return dc; }); } int rtcIsDataChannelOpen(int dc) { return wrap([dc] { return getDataChannel(dc)->isOpen() ? RTC_ERR_SUCCESS : RTC_ERR_FAILURE; }); } int rtcDeleteDataChannel(int dc) { return wrap([dc] { auto dataChannel = getDataChannel(dc); dataChannel->onOpen(nullptr); dataChannel->onClosed(nullptr); dataChannel->onError(nullptr); dataChannel->onMessage(nullptr); dataChannel->onBufferedAmountLow(nullptr); dataChannel->onAvailable(nullptr); eraseDataChannel(dc); return RTC_ERR_SUCCESS; }); } int rtcIsDataChannelOpen(int dc) { return wrap([dc] { return getDataChannel(dc)->isOpen() ? RTC_ERR_SUCCESS : RTC_ERR_FAILURE; }); } #if RTC_ENABLE_MEDIA void setSSRC(Description::Media *description, uint32_t ssrc, const char *_name, const char *_msid, const char *_trackID) { optional name = nullopt; if (_name) { name = string(_name); } optional msid = nullopt; if (_msid) { msid = string(_msid); } optional trackID = nullopt; if (_trackID) { trackID = string(_trackID); } description->addSSRC(ssrc, name, msid, trackID); } int rtcAddTrack(int pc, const char *mediaDescriptionSdp) { return wrap([&] { if (!mediaDescriptionSdp) throw std::invalid_argument("Unexpected null pointer for track media description"); auto peerConnection = getPeerConnection(pc); Description::Media media{string(mediaDescriptionSdp)}; int tr = emplaceTrack(peerConnection->addTrack(std::move(media))); if (auto ptr = getUserPointer(pc)) rtcSetUserPointer(tr, *ptr); return tr; }); } int rtcAddTrackEx(int pc, const rtcTrackInit *init) { return wrap([&] { auto peerConnection = getPeerConnection(pc); if (!init) throw std::invalid_argument("Unexpected null pointer for track init"); auto direction = static_cast(init->direction); string mid; if (init->mid) { mid = string(init->mid); } else { switch (init->codec) { case RTC_CODEC_H264: case RTC_CODEC_VP8: case RTC_CODEC_VP9: mid = "video"; break; case RTC_CODEC_OPUS: mid = "audio"; break; default: mid = "video"; break; } } optional optDescription = nullopt; switch (init->codec) { case RTC_CODEC_H264: case RTC_CODEC_VP8: case RTC_CODEC_VP9: { auto desc = Description::Video(mid, direction); switch (init->codec) { case RTC_CODEC_H264: desc.addH264Codec(init->payloadType); break; case RTC_CODEC_VP8: desc.addVP8Codec(init->payloadType); break; case RTC_CODEC_VP9: desc.addVP8Codec(init->payloadType); break; default: break; } optDescription = desc; break; } case RTC_CODEC_OPUS: { auto desc = Description::Audio(mid, direction); switch (init->codec) { case RTC_CODEC_OPUS: desc.addOpusCodec(init->payloadType); break; default: break; } optDescription = desc; break; } default: break; } if (!optDescription) throw std::invalid_argument("Unexpected codec"); auto desc = std::move(*optDescription); desc.addSSRC(init->ssrc, init->name ? std::make_optional(string(init->name)) : nullopt, init->msid ? std::make_optional(string(init->msid)) : nullopt, init->trackId ? std::make_optional(string(init->trackId)) : nullopt); int tr = emplaceTrack(peerConnection->addTrack(std::move(desc))); if (auto ptr = getUserPointer(pc)) rtcSetUserPointer(tr, *ptr); return tr; }); } int rtcDeleteTrack(int tr) { return wrap([&] { auto track = getTrack(tr); track->onOpen(nullptr); track->onClosed(nullptr); track->onError(nullptr); track->onMessage(nullptr); track->onBufferedAmountLow(nullptr); track->onAvailable(nullptr); eraseTrack(tr); return RTC_ERR_SUCCESS; }); } int rtcGetTrackDescription(int tr, char *buffer, int size) { return wrap([&] { auto track = getTrack(tr); return copyAndReturn(track->description(), buffer, size); }); } #if RTC_ENABLE_MEDIA void setSSRC(Description::Media *description, uint32_t ssrc, const char *_name, const char *_msid, const char *_trackID) { optional name = nullopt; if (_name) { name = string(_name); } optional msid = nullopt; if (_msid) { msid = string(_msid); } optional trackID = nullopt; if (_trackID) { trackID = string(_trackID); } description->addSSRC(ssrc, name, msid, trackID); } int rtcSetH264PacketizationHandler(int tr, const rtcPacketizationHandlerInit *init) { return wrap([&] { auto track = getTrack(tr); // create RTP configuration auto rtpConfig = createRtpPacketizationConfig(init); // create packetizer auto maxFragmentSize = init && init->maxFragmentSize ? init->maxFragmentSize : RTC_DEFAULT_MAXIMUM_FRAGMENT_SIZE; auto packetizer = std::make_shared(rtpConfig, maxFragmentSize); // create H264 handler auto h264Handler = std::make_shared(packetizer); emplaceMediaChainableHandler(h264Handler, tr); emplaceRtpConfig(rtpConfig, tr); // set handler track->setMediaHandler(h264Handler); return RTC_ERR_SUCCESS; }); } int rtcSetOpusPacketizationHandler(int tr, const rtcPacketizationHandlerInit *init) { return wrap([&] { auto track = getTrack(tr); // create RTP configuration auto rtpConfig = createRtpPacketizationConfig(init); // create packetizer auto packetizer = std::make_shared(rtpConfig); // create Opus handler auto opusHandler = std::make_shared(packetizer); emplaceMediaChainableHandler(opusHandler, tr); emplaceRtpConfig(rtpConfig, tr); // set handler track->setMediaHandler(opusHandler); return RTC_ERR_SUCCESS; }); } int rtcChainRtcpSrReporter(int tr) { return wrap([tr] { auto config = getRtpConfig(tr); auto reporter = std::make_shared(config); emplaceRtcpSrReporter(reporter, tr); auto chainableHandler = getMediaChainableHandler(tr); chainableHandler->addToChain(reporter); return RTC_ERR_SUCCESS; }); } int rtcChainRtcpNackResponder(int tr, unsigned int maxStoredPacketsCount) { return wrap([tr, maxStoredPacketsCount] { auto responder = std::make_shared(maxStoredPacketsCount); auto chainableHandler = getMediaChainableHandler(tr); chainableHandler->addToChain(responder); return RTC_ERR_SUCCESS; }); } int rtcSetRtpConfigurationStartTime(int id, const rtcStartTime *startTime) { return wrap([&] { auto config = getRtpConfig(id); auto epoch = startTime->since1970 ? RtpPacketizationConfig::EpochStart::T1970 : RtpPacketizationConfig::EpochStart::T1900; config->setStartTime(startTime->seconds, epoch, startTime->timestamp); return RTC_ERR_SUCCESS; }); } int rtcStartRtcpSenderReporterRecording(int id) { return wrap([id] { auto sender = getRtcpSrReporter(id); sender->startRecording(); return RTC_ERR_SUCCESS; }); } int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t *timestamp) { return wrap([&] { auto config = getRtpConfig(id); *timestamp = config->secondsToTimestamp(seconds); return RTC_ERR_SUCCESS; }); } int rtcTransformTimestampToSeconds(int id, uint32_t timestamp, double *seconds) { return wrap([&] { auto config = getRtpConfig(id); *seconds = config->timestampToSeconds(timestamp); return RTC_ERR_SUCCESS; }); } int rtcGetCurrentTrackTimestamp(int id, uint32_t *timestamp) { return wrap([&] { auto config = getRtpConfig(id); *timestamp = config->timestamp; return RTC_ERR_SUCCESS; }); } int rtcGetTrackStartTimestamp(int id, uint32_t *timestamp) { return wrap([&] { auto config = getRtpConfig(id); *timestamp = config->startTimestamp; return RTC_ERR_SUCCESS; }); } int rtcSetTrackRtpTimestamp(int id, uint32_t timestamp) { return wrap([&] { auto config = getRtpConfig(id); config->timestamp = timestamp; return RTC_ERR_SUCCESS; }); } int rtcGetPreviousTrackSenderReportTimestamp(int id, uint32_t *timestamp) { return wrap([&] { auto sender = getRtcpSrReporter(id); *timestamp = sender->previousReportedTimestamp; return RTC_ERR_SUCCESS; }); } int rtcSetNeedsToSendRtcpSr(int id) { return wrap([id] { auto sender = getRtcpSrReporter(id); sender->setNeedsToReport(); return RTC_ERR_SUCCESS; }); } int rtcGetTrackPayloadTypesForCodec(int tr, const char * ccodec, int * buffer, int size) { return wrap([&] { auto track = getTrack(tr); auto codec = lowercased(string(ccodec)); auto description = track->description(); std::vector payloadTypes{}; payloadTypes.reserve(std::max(size, 0)); for (auto it = description.beginMaps(); it != description.endMaps(); it++) { auto element = *it; if (lowercased(element.second.format) == codec) { payloadTypes.push_back(element.first); } } return copyAndReturn(payloadTypes, buffer, size); }); } #endif // RTC_ENABLE_MEDIA #if RTC_ENABLE_WEBSOCKET int rtcCreateWebSocket(const char *url) { return wrap([&] { auto ws = std::make_shared(); ws->open(url); return emplaceWebSocket(ws); }); } int rtcCreateWebSocketEx(const char *url, const rtcWsConfiguration *config) { return wrap([&] { WebSocket::Configuration c; c.disableTlsVerification = config->disableTlsVerification; auto ws = std::make_shared(c); ws->open(url); return emplaceWebSocket(ws); }); } int rtcDeleteWebsocket(int ws) { return wrap([&] { auto webSocket = getWebSocket(ws); webSocket->onOpen(nullptr); webSocket->onClosed(nullptr); webSocket->onError(nullptr); webSocket->onMessage(nullptr); webSocket->onBufferedAmountLow(nullptr); webSocket->onAvailable(nullptr); eraseWebSocket(ws); return RTC_ERR_SUCCESS; }); } #endif int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb) { return wrap([&] { auto peerConnection = getPeerConnection(pc); if (cb) peerConnection->onLocalDescription([pc, cb](Description desc) { if (auto ptr = getUserPointer(pc)) cb(pc, string(desc).c_str(), desc.typeString().c_str(), *ptr); }); else peerConnection->onLocalDescription(nullptr); return RTC_ERR_SUCCESS; }); } int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb) { return wrap([&] { auto peerConnection = getPeerConnection(pc); if (cb) peerConnection->onLocalCandidate([pc, cb](Candidate cand) { if (auto ptr = getUserPointer(pc)) cb(pc, cand.candidate().c_str(), cand.mid().c_str(), *ptr); }); else peerConnection->onLocalCandidate(nullptr); return RTC_ERR_SUCCESS; }); } int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb) { return wrap([&] { auto peerConnection = getPeerConnection(pc); if (cb) peerConnection->onStateChange([pc, cb](PeerConnection::State state) { if (auto ptr = getUserPointer(pc)) cb(pc, static_cast(state), *ptr); }); else peerConnection->onStateChange(nullptr); return RTC_ERR_SUCCESS; }); } int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb) { return wrap([&] { auto peerConnection = getPeerConnection(pc); if (cb) peerConnection->onGatheringStateChange([pc, cb](PeerConnection::GatheringState state) { if (auto ptr = getUserPointer(pc)) cb(pc, static_cast(state), *ptr); }); else peerConnection->onGatheringStateChange(nullptr); return RTC_ERR_SUCCESS; }); } int rtcSetSignalingStateChangeCallback(int pc, rtcSignalingStateCallbackFunc cb) { return wrap([&] { auto peerConnection = getPeerConnection(pc); if (cb) peerConnection->onSignalingStateChange([pc, cb](PeerConnection::SignalingState state) { if (auto ptr = getUserPointer(pc)) cb(pc, static_cast(state), *ptr); }); else peerConnection->onGatheringStateChange(nullptr); return RTC_ERR_SUCCESS; }); } int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb) { return wrap([&] { auto peerConnection = getPeerConnection(pc); if (cb) peerConnection->onDataChannel([pc, cb](shared_ptr dataChannel) { int dc = emplaceDataChannel(dataChannel); if (auto ptr = getUserPointer(pc)) { rtcSetUserPointer(dc, *ptr); cb(pc, dc, *ptr); } }); else peerConnection->onDataChannel(nullptr); return RTC_ERR_SUCCESS; }); } int rtcSetTrackCallback(int pc, rtcTrackCallbackFunc cb) { return wrap([&] { auto peerConnection = getPeerConnection(pc); if (cb) peerConnection->onTrack([pc, cb](shared_ptr track) { int tr = emplaceTrack(track); if (auto ptr = getUserPointer(pc)) { rtcSetUserPointer(tr, *ptr); cb(pc, tr, *ptr); } }); else peerConnection->onTrack(nullptr); return RTC_ERR_SUCCESS; }); } int rtcSetLocalDescription(int pc, const char *type) { return wrap([&] { auto peerConnection = getPeerConnection(pc); peerConnection->setLocalDescription(type ? Description::stringToType(type) : Description::Type::Unspec); return RTC_ERR_SUCCESS; }); } int rtcGetSsrcsForTrack(int tr, uint32_t * buffer, int bufferSize) { return wrap([&] { auto track = getTrack(tr); auto ssrcs = track->description().getSSRCs(); return copyAndReturn(ssrcs, buffer, bufferSize); }); } int rtcGetCNameForSsrc(int tr, uint32_t ssrc, char * cname, int cnameSize) { return wrap([&] { auto track = getTrack(tr); auto description = track->description(); auto optCName = description.getCNameForSsrc(ssrc); if (optCName.has_value()) { return copyAndReturn(optCName.value(), cname, cnameSize); } else { return 0; } }); } int rtcGetSsrcsForType(const char * mediaType, const char * sdp, uint32_t * buffer, int bufferSize) { return wrap([&] { auto type = lowercased(string(mediaType)); auto oldSDP = string(sdp); auto description = Description(oldSDP, "unspec"); auto mediaCount = description.mediaCount(); for (auto i = 0; i < mediaCount; i++) { if (std::holds_alternative(description.media(i))) { auto media = std::get(description.media(i)); auto currentMediaType = lowercased(media->type()); if (currentMediaType == type) { auto ssrcs = media->getSSRCs(); return copyAndReturn(ssrcs, buffer, bufferSize); } } } return 0; }); } int rtcSetSsrcForType(const char * mediaType, const char * sdp, char * buffer, const int bufferSize, rtcSsrcForTypeInit * init) { return wrap([&] { auto type = lowercased(string(mediaType)); auto prevSDP = string(sdp); auto description = Description(prevSDP, "unspec"); auto mediaCount = description.mediaCount(); for (auto i = 0; i < mediaCount; i++) { if (std::holds_alternative(description.media(i))) { auto media = std::get(description.media(i)); auto currentMediaType = lowercased(media->type()); if (currentMediaType == type) { setSSRC(media, init->ssrc, init->name, init->msid, init->trackId); break; } } } return copyAndReturn(string(description), buffer, bufferSize); }); } int rtcSetRemoteDescription(int pc, const char *sdp, const char *type) { return wrap([&] { auto peerConnection = getPeerConnection(pc); if (!sdp) throw std::invalid_argument("Unexpected null pointer for remote description"); peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""}); return RTC_ERR_SUCCESS; }); } int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) { return wrap([&] { auto peerConnection = getPeerConnection(pc); if (!cand) throw std::invalid_argument("Unexpected null pointer for remote candidate"); peerConnection->addRemoteCandidate({string(cand), mid ? string(mid) : ""}); return RTC_ERR_SUCCESS; }); } int rtcGetLocalDescription(int pc, char *buffer, int size) { return wrap([&] { auto peerConnection = getPeerConnection(pc); if (auto desc = peerConnection->localDescription()) return copyAndReturn(string(*desc), buffer, size); else return RTC_ERR_NOT_AVAIL; }); } int rtcGetRemoteDescription(int pc, char *buffer, int size) { return wrap([&] { auto peerConnection = getPeerConnection(pc); if (auto desc = peerConnection->remoteDescription()) return copyAndReturn(string(*desc), buffer, size); else return RTC_ERR_NOT_AVAIL; }); } int rtcGetLocalDescriptionType(int pc, char *buffer, int size) { return wrap([&] { auto peerConnection = getPeerConnection(pc); if (auto desc = peerConnection->localDescription()) return copyAndReturn(desc->typeString(), buffer, size); else return RTC_ERR_NOT_AVAIL; }); } int rtcGetRemoteDescriptionType(int pc, char *buffer, int size) { return wrap([&] { auto peerConnection = getPeerConnection(pc); if (auto desc = peerConnection->remoteDescription()) return copyAndReturn(desc->typeString(), buffer, size); else return RTC_ERR_NOT_AVAIL; }); } int rtcGetLocalAddress(int pc, char *buffer, int size) { return wrap([&] { auto peerConnection = getPeerConnection(pc); if (auto addr = peerConnection->localAddress()) return copyAndReturn(std::move(*addr), buffer, size); else return RTC_ERR_NOT_AVAIL; }); } int rtcGetRemoteAddress(int pc, char *buffer, int size) { return wrap([&] { auto peerConnection = getPeerConnection(pc); if (auto addr = peerConnection->remoteAddress()) return copyAndReturn(std::move(*addr), buffer, size); else return RTC_ERR_NOT_AVAIL; }); } int rtcGetSelectedCandidatePair(int pc, char *local, int localSize, char *remote, int remoteSize) { return wrap([&] { auto peerConnection = getPeerConnection(pc); Candidate localCand; Candidate remoteCand; if (!peerConnection->getSelectedCandidatePair(&localCand, &remoteCand)) return RTC_ERR_NOT_AVAIL; int localRet = copyAndReturn(string(localCand), local, localSize); if (localRet < 0) return localRet; int remoteRet = copyAndReturn(string(remoteCand), remote, remoteSize); if (remoteRet < 0) return remoteRet; return std::max(localRet, remoteRet); }); } int rtcGetDataChannelStream(int dc) { return wrap([dc] { auto dataChannel = getDataChannel(dc); return int(dataChannel->id()); }); } int rtcGetDataChannelLabel(int dc, char *buffer, int size) { return wrap([&] { auto dataChannel = getDataChannel(dc); return copyAndReturn(dataChannel->label(), buffer, size); }); } int rtcGetDataChannelProtocol(int dc, char *buffer, int size) { return wrap([&] { auto dataChannel = getDataChannel(dc); return copyAndReturn(dataChannel->protocol(), buffer, size); }); } int rtcGetDataChannelReliability(int dc, rtcReliability *reliability) { return wrap([&] { auto dataChannel = getDataChannel(dc); if (!reliability) throw std::invalid_argument("Unexpected null pointer for reliability"); Reliability dcr = dataChannel->reliability(); std::memset(reliability, 0, sizeof(*reliability)); reliability->unordered = dcr.unordered; if (dcr.type == Reliability::Type::Timed) { reliability->unreliable = true; reliability->maxPacketLifeTime = int(std::get(dcr.rexmit).count()); } else if (dcr.type == Reliability::Type::Rexmit) { reliability->unreliable = true; reliability->maxRetransmits = std::get(dcr.rexmit); } else { reliability->unreliable = false; } return RTC_ERR_SUCCESS; }); } int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb) { return wrap([&] { auto channel = getChannel(id); if (cb) channel->onOpen([id, cb]() { if (auto ptr = getUserPointer(id)) cb(id, *ptr); }); else channel->onOpen(nullptr); return RTC_ERR_SUCCESS; }); } int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb) { return wrap([&] { auto channel = getChannel(id); if (cb) channel->onClosed([id, cb]() { if (auto ptr = getUserPointer(id)) cb(id, *ptr); }); else channel->onClosed(nullptr); return RTC_ERR_SUCCESS; }); } int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb) { return wrap([&] { auto channel = getChannel(id); if (cb) channel->onError([id, cb](string error) { if (auto ptr = getUserPointer(id)) cb(id, error.c_str(), *ptr); }); else channel->onError(nullptr); return RTC_ERR_SUCCESS; }); } int rtcSetMessageCallback(int id, rtcMessageCallbackFunc cb) { return wrap([&] { auto channel = getChannel(id); if (cb) channel->onMessage( [id, cb](binary b) { if (auto ptr = getUserPointer(id)) cb(id, reinterpret_cast(b.data()), int(b.size()), *ptr); }, [id, cb](string s) { if (auto ptr = getUserPointer(id)) cb(id, s.c_str(), -int(s.size() + 1), *ptr); }); else channel->onMessage(nullptr); return RTC_ERR_SUCCESS; }); } int rtcSendMessage(int id, const char *data, int size) { return wrap([&] { auto channel = getChannel(id); if (!data && size != 0) throw std::invalid_argument("Unexpected null pointer for data"); if (size >= 0) { auto b = reinterpret_cast(data); channel->send(binary(b, b + size)); return size; } else { string str(data); int len = int(str.size()); channel->send(std::move(str)); return len; } }); } int rtcGetBufferedAmount(int id) { return wrap([id] { auto channel = getChannel(id); return int(channel->bufferedAmount()); }); } int rtcSetBufferedAmountLowThreshold(int id, int amount) { return wrap([&] { auto channel = getChannel(id); channel->setBufferedAmountLowThreshold(size_t(amount)); return RTC_ERR_SUCCESS; }); } int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb) { return wrap([&] { auto channel = getChannel(id); if (cb) channel->onBufferedAmountLow([id, cb]() { if (auto ptr = getUserPointer(id)) cb(id, *ptr); }); else channel->onBufferedAmountLow(nullptr); return RTC_ERR_SUCCESS; }); } int rtcGetAvailableAmount(int id) { return wrap([id] { return int(getChannel(id)->availableAmount()); }); } int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb) { return wrap([&] { auto channel = getChannel(id); if (cb) channel->onAvailable([id, cb]() { if (auto ptr = getUserPointer(id)) cb(id, *ptr); }); else channel->onAvailable(nullptr); return RTC_ERR_SUCCESS; }); } int rtcReceiveMessage(int id, char *buffer, int *size) { return wrap([&] { auto channel = getChannel(id); if (!size) throw std::invalid_argument("Unexpected null pointer for size"); *size = std::abs(*size); auto message = channel->peek(); if (!message) return RTC_ERR_NOT_AVAIL; return std::visit( // overloaded{ [&](binary b) { int ret = copyAndReturn(std::move(b), buffer, *size); if (ret >= 0) { channel->receive(); // discard *size = ret; return RTC_ERR_SUCCESS; } else { *size = int(b.size()); return ret; } }, [&](string s) { int ret = copyAndReturn(std::move(s), buffer, *size); if (ret >= 0) { channel->receive(); // discard *size = -ret; return RTC_ERR_SUCCESS; } else { *size = -int(s.size() + 1); return ret; } }, }, *message); }); } void rtcPreload() { rtc::Preload(); } void rtcCleanup() { rtc::Cleanup(); } int rtcSetSctpSettings(const rtcSctpSettings *settings) { return wrap([&] { SctpSettings s = {}; if (settings->recvBufferSize > 0) s.recvBufferSize = size_t(settings->recvBufferSize); if (settings->sendBufferSize > 0) s.sendBufferSize = size_t(settings->sendBufferSize); if (settings->maxChunksOnQueue > 0) s.maxChunksOnQueue = size_t(settings->maxChunksOnQueue); if (settings->initialCongestionWindow > 0) s.initialCongestionWindow = size_t(settings->initialCongestionWindow); if (settings->maxBurst > 0) s.maxBurst = size_t(settings->maxBurst); else if (settings->maxBurst < 0) s.maxBurst = size_t(0); // setting to 0 disables, not setting chooses optimized default if (settings->congestionControlModule >= 0) s.congestionControlModule = unsigned(settings->congestionControlModule); if (settings->delayedSackTimeMs > 0) s.delayedSackTime = std::chrono::milliseconds(settings->delayedSackTimeMs); else if (settings->delayedSackTimeMs < 0) s.delayedSackTime = std::chrono::milliseconds(0); if (settings->minRetransmitTimeoutMs > 0) s.minRetransmitTimeout = std::chrono::milliseconds(settings->minRetransmitTimeoutMs); if (settings->maxRetransmitTimeoutMs > 0) s.maxRetransmitTimeout = std::chrono::milliseconds(settings->maxRetransmitTimeoutMs); if (settings->initialRetransmitTimeoutMs > 0) s.initialRetransmitTimeout = std::chrono::milliseconds(settings->initialRetransmitTimeoutMs); if (settings->maxRetransmitAttempts > 0) s.maxRetransmitAttempts = settings->maxRetransmitAttempts; if (settings->heartbeatIntervalMs > 0) s.heartbeatInterval = std::chrono::milliseconds(settings->heartbeatIntervalMs); SetSctpSettings(std::move(s)); return RTC_ERR_SUCCESS; }); }