Browse Source

Merge pull request #1320 from paullouisageneau/createoffer

Add createOffer() and createAnswer()
Paul-Louis Ageneau 6 months ago
parent
commit
6fa7cffe63

+ 22 - 3
DOC.md

@@ -207,7 +207,9 @@ Initiates the handshake process. Following this call, the local description call
 Arguments:
 Arguments:
 
 
 - `pc`: the Peer Connection identifier
 - `pc`: the Peer Connection identifier
-- `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for autodetection.
+- `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for automatic (recommended).
+
+Warning: This function expects the optional type for the local description and not an SDP description. It is not possible to set an existing SDP description.
 
 
 #### rtcSetRemoteDescription
 #### rtcSetRemoteDescription
 
 
@@ -220,7 +222,8 @@ Sets the remote description received from the remote peer by the user's method o
 Arguments:
 Arguments:
 
 
 - `pc`: the Peer Connection identifier
 - `pc`: the Peer Connection identifier
-- `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for autodetection.
+- `sdp`: the remote description in SDP format
+- `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for automatic (not recommended).
 
 
 If the remote description is an offer and `disableAutoNegotiation` was not set in `rtcConfiguration`, the library will automatically answer by calling `rtcSetLocalDescription` internally. Otherwise, the user must call it to answer the remote description.
 If the remote description is an offer and `disableAutoNegotiation` was not set in `rtcConfiguration`, the library will automatically answer by calling `rtcSetLocalDescription` internally. Otherwise, the user must call it to answer the remote description.
 
 
@@ -296,7 +299,7 @@ Return value: the length of the string copied in buffer (including the terminati
 
 
 If `buffer` is `NULL`, the description is not copied but the size is still returned.
 If `buffer` is `NULL`, the description is not copied but the size is still returned.
 
 
-#### rtcGetRemoteDescription
+#### rtcGetRemoteDescriptionType
 
 
 ```
 ```
 int rtcGetRemoteDescriptionType(int pc, char *buffer, int size)
 int rtcGetRemoteDescriptionType(int pc, char *buffer, int size)
@@ -314,6 +317,22 @@ Return value: the length of the string copied in buffer (including the terminati
 
 
 If `buffer` is `NULL`, the description is not copied but the size is still returned.
 If `buffer` is `NULL`, the description is not copied but the size is still returned.
 
 
+#### rtcCreateOffer/rtcCreateAnswer
+
+```
+int rtcCreateOffer(int pc, char *buffer, int size)
+int rtcCreateAnswer(int pc, char *buffer, int size)
+```
+
+Create a local offer or answer description in SDP format. These functions are intended only for specific use cases where the application needs to generate a description without setting it. It is useless to call them before `rtcSetLocalDescription` as it doesn't expect the user to supply a description.
+
+- `pc`: the Peer Connection identifier
+- `buffer`: a user-supplied buffer to store the description
+- `size`: the size of `buffer`
+
+Return value: the length of the string copied in buffer (including the terminating null character) or a negative error code
+
+If `buffer` is `NULL`, the description is not copied but the size is still returned.
 
 
 #### rtcGetLocalAddress
 #### rtcGetLocalAddress
 
 

+ 2 - 1
include/rtc/description.hpp

@@ -66,9 +66,10 @@ public:
 	bool ended() const;
 	bool ended() const;
 
 
 	void hintType(Type type);
 	void hintType(Type type);
-	void setFingerprint(CertificateFingerprint f);
 	void addIceOption(string option);
 	void addIceOption(string option);
 	void removeIceOption(const string &option);
 	void removeIceOption(const string &option);
+	void setIceAttribute(string ufrag, string pwd);
+	void setFingerprint(CertificateFingerprint f);
 
 
 	std::vector<string> attributes() const;
 	std::vector<string> attributes() const;
 	void addAttribute(string attr);
 	void addAttribute(string attr);

+ 5 - 1
include/rtc/peerconnection.hpp

@@ -97,9 +97,13 @@ public:
 	bool getSelectedCandidatePair(Candidate *local, Candidate *remote);
 	bool getSelectedCandidatePair(Candidate *local, Candidate *remote);
 
 
 	void setLocalDescription(Description::Type type = Description::Type::Unspec, LocalDescriptionInit init = {});
 	void setLocalDescription(Description::Type type = Description::Type::Unspec, LocalDescriptionInit init = {});
+	void gatherLocalCandidates(std::vector<IceServer> additionalIceServers = {});
 	void setRemoteDescription(Description description);
 	void setRemoteDescription(Description description);
 	void addRemoteCandidate(Candidate candidate);
 	void addRemoteCandidate(Candidate candidate);
-	void gatherLocalCandidates(std::vector<IceServer> additionalIceServers = {});
+
+	// For specific use cases only
+	Description createOffer();
+	Description createAnswer();
 
 
 	void setMediaHandler(shared_ptr<MediaHandler> handler);
 	void setMediaHandler(shared_ptr<MediaHandler> handler);
 	shared_ptr<MediaHandler> getMediaHandler();
 	shared_ptr<MediaHandler> getMediaHandler();

+ 5 - 1
include/rtc/rtc.h

@@ -211,7 +211,7 @@ RTC_C_EXPORT int rtcSetIceStateChangeCallback(int pc, rtcIceStateChangeCallbackF
 RTC_C_EXPORT int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb);
 RTC_C_EXPORT int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb);
 RTC_C_EXPORT int rtcSetSignalingStateChangeCallback(int pc, rtcSignalingStateCallbackFunc cb);
 RTC_C_EXPORT int rtcSetSignalingStateChangeCallback(int pc, rtcSignalingStateCallbackFunc cb);
 
 
-RTC_C_EXPORT int rtcSetLocalDescription(int pc, const char *type);
+RTC_C_EXPORT int rtcSetLocalDescription(int pc, const char *type); // type may be NULL
 RTC_C_EXPORT int rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
 RTC_C_EXPORT int rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
 RTC_C_EXPORT int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid);
 RTC_C_EXPORT int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid);
 
 
@@ -221,6 +221,10 @@ RTC_C_EXPORT int rtcGetRemoteDescription(int pc, char *buffer, int size);
 RTC_C_EXPORT int rtcGetLocalDescriptionType(int pc, char *buffer, int size);
 RTC_C_EXPORT int rtcGetLocalDescriptionType(int pc, char *buffer, int size);
 RTC_C_EXPORT int rtcGetRemoteDescriptionType(int pc, char *buffer, int size);
 RTC_C_EXPORT int rtcGetRemoteDescriptionType(int pc, char *buffer, int size);
 
 
+// For specific use cases only
+RTC_C_EXPORT int rtcCreateOffer(int pc, char *buffer, int size);
+RTC_C_EXPORT int rtcCreateAnswer(int pc, char *buffer, int size);
+
 RTC_C_EXPORT int rtcGetLocalAddress(int pc, char *buffer, int size);
 RTC_C_EXPORT int rtcGetLocalAddress(int pc, char *buffer, int size);
 RTC_C_EXPORT int rtcGetRemoteAddress(int pc, char *buffer, int size);
 RTC_C_EXPORT int rtcGetRemoteAddress(int pc, char *buffer, int size);
 
 

+ 22 - 3
pages/content/pages/reference.md

@@ -210,7 +210,9 @@ Initiates the handshake process. Following this call, the local description call
 Arguments:
 Arguments:
 
 
 - `pc`: the Peer Connection identifier
 - `pc`: the Peer Connection identifier
-- `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for autodetection.
+- `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for automatic (recommended).
+
+Warning: This function expects the optional type for the local description and not an SDP description. It is not possible to set an existing SDP description.
 
 
 #### rtcSetRemoteDescription
 #### rtcSetRemoteDescription
 
 
@@ -223,7 +225,8 @@ Sets the remote description received from the remote peer by the user's method o
 Arguments:
 Arguments:
 
 
 - `pc`: the Peer Connection identifier
 - `pc`: the Peer Connection identifier
-- `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for autodetection.
+- `sdp`: the remote description in SDP format
+- `type` (optional): type of the description ("offer", "answer", "pranswer", or "rollback") or NULL for automatic (not recommended).
 
 
 If the remote description is an offer and `disableAutoNegotiation` was not set in `rtcConfiguration`, the library will automatically answer by calling `rtcSetLocalDescription` internally. Otherwise, the user must call it to answer the remote description.
 If the remote description is an offer and `disableAutoNegotiation` was not set in `rtcConfiguration`, the library will automatically answer by calling `rtcSetLocalDescription` internally. Otherwise, the user must call it to answer the remote description.
 
 
@@ -299,7 +302,7 @@ Return value: the length of the string copied in buffer (including the terminati
 
 
 If `buffer` is `NULL`, the description is not copied but the size is still returned.
 If `buffer` is `NULL`, the description is not copied but the size is still returned.
 
 
-#### rtcGetRemoteDescription
+#### rtcGetRemoteDescriptionType
 
 
 ```
 ```
 int rtcGetRemoteDescriptionType(int pc, char *buffer, int size)
 int rtcGetRemoteDescriptionType(int pc, char *buffer, int size)
@@ -317,6 +320,22 @@ Return value: the length of the string copied in buffer (including the terminati
 
 
 If `buffer` is `NULL`, the description is not copied but the size is still returned.
 If `buffer` is `NULL`, the description is not copied but the size is still returned.
 
 
+#### rtcCreateOffer/rtcCreateAnswer
+
+```
+int rtcCreateOffer(int pc, char *buffer, int size)
+int rtcCreateAnswer(int pc, char *buffer, int size)
+```
+
+Create a local offer or answer description in SDP format. These functions are intended only for specific use cases where the application needs to generate a description without setting it. It is useless to call them before `rtcSetLocalDescription` as it doesn't expect the user to supply a description.
+
+- `pc`: the Peer Connection identifier
+- `buffer`: a user-supplied buffer to store the description
+- `size`: the size of `buffer`
+
+Return value: the length of the string copied in buffer (including the terminating null character) or a negative error code
+
+If `buffer` is `NULL`, the description is not copied but the size is still returned.
 
 
 #### rtcGetLocalAddress
 #### rtcGetLocalAddress
 
 

+ 18 - 0
src/capi.cpp

@@ -631,6 +631,24 @@ int rtcGetRemoteDescriptionType(int pc, char *buffer, int size) {
 	});
 	});
 }
 }
 
 
+int rtcCreateOffer(int pc, char *buffer, int size) {
+	return wrap([&] {
+		auto peerConnection = getPeerConnection(pc);
+
+		auto desc = peerConnection->createOffer();
+		return copyAndReturn(string(desc), buffer, size);
+	});
+}
+
+int rtcCreateAnswer(int pc, char *buffer, int size) {
+	return wrap([&] {
+		auto peerConnection = getPeerConnection(pc);
+
+		auto desc = peerConnection->createAnswer();
+		return copyAndReturn(string(desc), buffer, size);
+	});
+}
+
 int rtcGetLocalAddress(int pc, char *buffer, int size) {
 int rtcGetLocalAddress(int pc, char *buffer, int size) {
 	return wrap([&] {
 	return wrap([&] {
 		auto peerConnection = getPeerConnection(pc);
 		auto peerConnection = getPeerConnection(pc);

+ 15 - 10
src/description.cpp

@@ -216,6 +216,21 @@ void Description::hintType(Type type) {
 		mType = type;
 		mType = type;
 }
 }
 
 
+void Description::addIceOption(string option) {
+	if (std::find(mIceOptions.begin(), mIceOptions.end(), option) == mIceOptions.end())
+		mIceOptions.emplace_back(std::move(option));
+}
+
+void Description::removeIceOption(const string &option) {
+	mIceOptions.erase(std::remove(mIceOptions.begin(), mIceOptions.end(), option),
+	                  mIceOptions.end());
+}
+
+void Description::setIceAttribute(string ufrag, string pwd) {
+	mIceUfrag = std::move(ufrag);
+	mIcePwd = std::move(pwd);
+}
+
 void Description::setFingerprint(CertificateFingerprint f) {
 void Description::setFingerprint(CertificateFingerprint f) {
 	if (!f.isValid())
 	if (!f.isValid())
 		throw std::invalid_argument("Invalid " +
 		throw std::invalid_argument("Invalid " +
@@ -227,16 +242,6 @@ void Description::setFingerprint(CertificateFingerprint f) {
 	mFingerprint = std::move(f);
 	mFingerprint = std::move(f);
 }
 }
 
 
-void Description::addIceOption(string option) {
-	if (std::find(mIceOptions.begin(), mIceOptions.end(), option) == mIceOptions.end())
-		mIceOptions.emplace_back(std::move(option));
-}
-
-void Description::removeIceOption(const string &option) {
-	mIceOptions.erase(std::remove(mIceOptions.begin(), mIceOptions.end(), option),
-	                  mIceOptions.end());
-}
-
 std::vector<string> Description::Entry::attributes() const { return mAttributes; }
 std::vector<string> Description::Entry::attributes() const { return mAttributes; }
 
 
 void Description::Entry::addAttribute(string attr) {
 void Description::Entry::addAttribute(string attr) {

+ 67 - 74
src/impl/peerconnection.cpp

@@ -925,7 +925,7 @@ void PeerConnection::validateRemoteDescription(const Description &description) {
 	PLOG_VERBOSE << "Remote description looks valid";
 	PLOG_VERBOSE << "Remote description looks valid";
 }
 }
 
 
-void PeerConnection::processLocalDescription(Description description) {
+void PeerConnection::populateLocalDescription(Description &description) const {
 	const uint16_t localSctpPort = DEFAULT_SCTP_PORT;
 	const uint16_t localSctpPort = DEFAULT_SCTP_PORT;
 	const size_t localMaxMessageSize =
 	const size_t localMaxMessageSize =
 	    config.maxMessageSize.value_or(DEFAULT_LOCAL_MAX_MESSAGE_SIZE);
 	    config.maxMessageSize.value_or(DEFAULT_LOCAL_MAX_MESSAGE_SIZE);
@@ -935,7 +935,7 @@ void PeerConnection::processLocalDescription(Description description) {
 
 
 	if (auto remote = remoteDescription()) {
 	if (auto remote = remoteDescription()) {
 		// Reciprocate remote description
 		// Reciprocate remote description
-		for (int i = 0; i < remote->mediaCount(); ++i)
+		for (int i = 0; i < remote->mediaCount(); ++i) {
 			std::visit( // reciprocate each media
 			std::visit( // reciprocate each media
 			    rtc::overloaded{
 			    rtc::overloaded{
 			        [&](Description::Application *remoteApp) {
 			        [&](Description::Application *remoteApp) {
@@ -950,78 +950,46 @@ void PeerConnection::processLocalDescription(Description description) {
 					                   << app.mid() << "\"";
 					                   << app.mid() << "\"";
 
 
 					        description.addMedia(std::move(app));
 					        description.addMedia(std::move(app));
-					        return;
-				        }
 
 
-				        auto reciprocated = remoteApp->reciprocate();
-				        reciprocated.hintSctpPort(localSctpPort);
-				        reciprocated.setMaxMessageSize(localMaxMessageSize);
+				        } else {
+							auto reciprocated = remoteApp->reciprocate();
+							reciprocated.hintSctpPort(localSctpPort);
+							reciprocated.setMaxMessageSize(localMaxMessageSize);
 
 
-				        PLOG_DEBUG << "Reciprocating application in local description, mid=\""
-				                   << reciprocated.mid() << "\"";
+							PLOG_DEBUG << "Reciprocating application in local description, mid=\""
+								       << reciprocated.mid() << "\"";
 
 
-				        description.addMedia(std::move(reciprocated));
+							description.addMedia(std::move(reciprocated));
+						}
 			        },
 			        },
 			        [&](Description::Media *remoteMedia) {
 			        [&](Description::Media *remoteMedia) {
-				        std::unique_lock lock(mTracksMutex); // we may emplace a track
-				        if (auto it = mTracks.find(remoteMedia->mid()); it != mTracks.end()) {
-					        // Prefer local description
-					        if (auto track = it->second.lock()) {
-						        auto media = track->description();
-
-						        PLOG_DEBUG << "Adding media to local description, mid=\""
-						                   << media.mid() << "\", removed=" << std::boolalpha
-						                   << media.isRemoved();
-
-						        description.addMedia(std::move(media));
-
-					        } else {
-						        auto reciprocated = remoteMedia->reciprocate();
-						        reciprocated.markRemoved();
+				        std::shared_lock lock(mTracksMutex);
+				        auto it = mTracks.find(remoteMedia->mid());
+					    auto track = it != mTracks.end() ? it->second.lock() : nullptr;
+						if(track) {
+							// Prefer local description
+							auto media = track->description();
 
 
-						        PLOG_DEBUG << "Adding media to local description, mid=\""
-						                   << reciprocated.mid()
-						                   << "\", removed=true (track is destroyed)";
-
-						        description.addMedia(std::move(reciprocated));
-					        }
-					        return;
-				        }
-
-				        auto reciprocated = remoteMedia->reciprocate();
-#if !RTC_ENABLE_MEDIA
-				        if (!reciprocated.isRemoved()) {
-					        // No media support, mark as removed
-					        PLOG_WARNING << "Rejecting track (not compiled with media support)";
-					        reciprocated.markRemoved();
-				        }
-#endif
+						    PLOG_DEBUG << "Adding media to local description, mid=\""
+						                << media.mid() << "\", removed=" << std::boolalpha
+						                << media.isRemoved();
 
 
-				        PLOG_DEBUG << "Reciprocating media in local description, mid=\""
-				                   << reciprocated.mid() << "\", removed=" << std::boolalpha
-				                   << reciprocated.isRemoved();
+						    description.addMedia(std::move(media));
 
 
-				        // Create incoming track
-				        auto track =
-				            std::make_shared<Track>(weak_from_this(), std::move(reciprocated));
-				        mTracks.emplace(std::make_pair(track->mid(), track));
-				        mTrackLines.emplace_back(track);
-				        triggerTrack(track); // The user may modify the track description
+					    } else {
+							auto reciprocated = remoteMedia->reciprocate();
+							reciprocated.markRemoved();
 
 
-				        auto handler = getMediaHandler();
-				        if (handler)
-					        handler->media(track->description());
+						    PLOG_DEBUG << "Adding media to local description, mid=\""
+						                << reciprocated.mid()
+						                << "\", removed=true (track is destroyed)";
 
 
-				        if (track->description().isRemoved())
-					        track->close();
-
-				        description.addMedia(track->description());
+						    description.addMedia(std::move(reciprocated));
+					    }
 			        },
 			        },
 			    },
 			    },
 			    remote->media(i));
 			    remote->media(i));
-
-		// We need to update the SSRC cache for newly-created incoming tracks
-		updateTrackSsrcCache(*remote);
+		}
 	}
 	}
 
 
 	if (description.type() == Description::Type::Offer) {
 	if (description.type() == Description::Type::Offer) {
@@ -1061,22 +1029,18 @@ void PeerConnection::processLocalDescription(Description description) {
 				description.addMedia(std::move(app));
 				description.addMedia(std::move(app));
 			}
 			}
 		}
 		}
-
-		// There might be no media at this point, for instance if the user deleted tracks
-		if (description.mediaCount() == 0)
-			throw std::runtime_error("No DataChannel or Track to negotiate");
 	}
 	}
 
 
 	// Set local fingerprint (wait for certificate if necessary)
 	// Set local fingerprint (wait for certificate if necessary)
 	description.setFingerprint(mCertificate.get()->fingerprint());
 	description.setFingerprint(mCertificate.get()->fingerprint());
+}
 
 
+void PeerConnection::processLocalDescription(Description description) {
 	PLOG_VERBOSE << "Issuing local description: " << description;
 	PLOG_VERBOSE << "Issuing local description: " << description;
 
 
 	if (description.mediaCount() == 0)
 	if (description.mediaCount() == 0)
 		throw std::logic_error("Local description has no media line");
 		throw std::logic_error("Local description has no media line");
 
 
-	updateTrackSsrcCache(description);
-
 	{
 	{
 		// Set as local description
 		// Set as local description
 		std::lock_guard lock(mLocalDescriptionMutex);
 		std::lock_guard lock(mLocalDescriptionMutex);
@@ -1093,11 +1057,6 @@ void PeerConnection::processLocalDescription(Description description) {
 
 
 	mProcessor.enqueue(&PeerConnection::trigger<Description>, shared_from_this(),
 	mProcessor.enqueue(&PeerConnection::trigger<Description>, shared_from_this(),
 	                   &localDescriptionCallback, std::move(description));
 	                   &localDescriptionCallback, std::move(description));
-
-	// Reciprocated tracks might need to be open
-	if (auto dtlsTransport = std::atomic_load(&mDtlsTransport);
-	    dtlsTransport && dtlsTransport->state() == Transport::State::Connected)
-		mProcessor.enqueue(&PeerConnection::openTracks, shared_from_this());
 }
 }
 
 
 void PeerConnection::processLocalCandidate(Candidate candidate) {
 void PeerConnection::processLocalCandidate(Candidate candidate) {
@@ -1121,6 +1080,40 @@ void PeerConnection::processLocalCandidate(Candidate candidate) {
 }
 }
 
 
 void PeerConnection::processRemoteDescription(Description description) {
 void PeerConnection::processRemoteDescription(Description description) {
+	// Create tracks from remote description
+	for (int i = 0; i < description.mediaCount(); ++i) {
+		if (std::holds_alternative<Description::Media *>(description.media(i))) {
+			auto remoteMedia = std::get<Description::Media *>(description.media(i));
+			std::unique_lock lock(mTracksMutex); // we may emplace a track
+			if (auto it = mTracks.find(remoteMedia->mid()); it != mTracks.end())
+				continue;
+
+			PLOG_DEBUG << "New remote track, mid=\"" << remoteMedia->mid() << "\"";
+
+			auto reciprocated = remoteMedia->reciprocate();
+#if !RTC_ENABLE_MEDIA
+			if (!reciprocated.isRemoved()) {
+				// No media support, mark as removed
+				PLOG_WARNING << "Rejecting track (not compiled with media support)";
+				reciprocated.markRemoved();
+			}
+#endif
+
+			// Create incoming track
+			auto track = std::make_shared<Track>(weak_from_this(), std::move(reciprocated));
+			mTracks.emplace(std::make_pair(track->mid(), track));
+			mTrackLines.emplace_back(track);
+			triggerTrack(track); // The user may modify the track description
+
+			auto handler = getMediaHandler();
+			if (handler)
+				handler->media(track->description());
+
+			if (track->description().isRemoved())
+				track->close();
+		}
+	}
+
 	// Update the SSRC cache for existing tracks
 	// Update the SSRC cache for existing tracks
 	updateTrackSsrcCache(description);
 	updateTrackSsrcCache(description);
 
 
@@ -1146,9 +1139,9 @@ void PeerConnection::processRemoteDescription(Description description) {
 		mProcessor.enqueue(&PeerConnection::remoteCloseDataChannels, shared_from_this());
 		mProcessor.enqueue(&PeerConnection::remoteCloseDataChannels, shared_from_this());
 	}
 	}
 
 
+	// Reciprocated tracks might need to be open
 	if (dtlsTransport && dtlsTransport->state() == Transport::State::Connected)
 	if (dtlsTransport && dtlsTransport->state() == Transport::State::Connected)
 		mProcessor.enqueue(&PeerConnection::openTracks, shared_from_this());
 		mProcessor.enqueue(&PeerConnection::openTracks, shared_from_this());
-
 }
 }
 
 
 void PeerConnection::processRemoteCandidate(Candidate candidate) {
 void PeerConnection::processRemoteCandidate(Candidate candidate) {
@@ -1238,7 +1231,7 @@ void PeerConnection::setMediaHandler(shared_ptr<MediaHandler> handler) {
 	mMediaHandler = handler;
 	mMediaHandler = handler;
 }
 }
 
 
-shared_ptr<MediaHandler> PeerConnection::getMediaHandler() {
+shared_ptr<MediaHandler> PeerConnection::getMediaHandler() const {
 	std::shared_lock lock(mMediaHandlerMutex);
 	std::shared_lock lock(mMediaHandlerMutex);
 	return mMediaHandler;
 	return mMediaHandler;
 }
 }

+ 2 - 1
src/impl/peerconnection.hpp

@@ -75,6 +75,7 @@ struct PeerConnection : std::enable_shared_from_this<PeerConnection> {
 	void closeTracks();
 	void closeTracks();
 
 
 	void validateRemoteDescription(const Description &description);
 	void validateRemoteDescription(const Description &description);
+	void populateLocalDescription(Description &description) const;
 	void processLocalDescription(Description description);
 	void processLocalDescription(Description description);
 	void processLocalCandidate(Candidate candidate);
 	void processLocalCandidate(Candidate candidate);
 	void processRemoteDescription(Description description);
 	void processRemoteDescription(Description description);
@@ -84,7 +85,7 @@ struct PeerConnection : std::enable_shared_from_this<PeerConnection> {
 	bool negotiationNeeded() const;
 	bool negotiationNeeded() const;
 
 
 	void setMediaHandler(shared_ptr<MediaHandler> handler);
 	void setMediaHandler(shared_ptr<MediaHandler> handler);
-	shared_ptr<MediaHandler> getMediaHandler();
+	shared_ptr<MediaHandler> getMediaHandler() const;
 
 
 	void triggerDataChannel(weak_ptr<DataChannel> weakDataChannel);
 	void triggerDataChannel(weak_ptr<DataChannel> weakDataChannel);
 	void triggerTrack(weak_ptr<Track> weakTrack);
 	void triggerTrack(weak_ptr<Track> weakTrack);

+ 31 - 5
src/peerconnection.cpp

@@ -85,6 +85,7 @@ void PeerConnection::setLocalDescription(Description::Type type, LocalDescriptio
 	PLOG_VERBOSE << "Setting local description, type=" << Description::typeToString(type);
 	PLOG_VERBOSE << "Setting local description, type=" << Description::typeToString(type);
 
 
 	SignalingState signalingState = impl()->signalingState.load();
 	SignalingState signalingState = impl()->signalingState.load();
+
 	if (type == Description::Type::Rollback) {
 	if (type == Description::Type::Rollback) {
 		if (signalingState == SignalingState::HaveLocalOffer ||
 		if (signalingState == SignalingState::HaveLocalOffer ||
 		    signalingState == SignalingState::HaveLocalPranswer) {
 		    signalingState == SignalingState::HaveLocalPranswer) {
@@ -139,11 +140,17 @@ void PeerConnection::setLocalDescription(Description::Type type, LocalDescriptio
 		return; // closed
 		return; // closed
 
 
 	if (init.iceUfrag && init.icePwd) {
 	if (init.iceUfrag && init.icePwd) {
-		PLOG_DEBUG << "Using custom ICE attributes, ufrag=\"" << init.iceUfrag.value() << "\", pwd=\"" << init.icePwd.value() << "\"";
-		iceTransport->setIceAttributes(init.iceUfrag.value(), init.icePwd.value());
+		PLOG_DEBUG << "Setting custom ICE attributes, ufrag=\"" << *init.iceUfrag << "\", pwd=\"" << *init.icePwd << "\"";
+		iceTransport->setIceAttributes(*init.iceUfrag, *init.icePwd);
 	}
 	}
 
 
 	Description local = iceTransport->getLocalDescription(type);
 	Description local = iceTransport->getLocalDescription(type);
+	impl()->populateLocalDescription(local);
+
+	// There might be no media at this point, for instance if the user deleted tracks
+	if (local.mediaCount() == 0)
+		throw std::runtime_error("No DataChannel or Track to negotiate");
+
 	impl()->processLocalDescription(std::move(local));
 	impl()->processLocalDescription(std::move(local));
 
 
 	impl()->changeSignalingState(newSignalingState);
 	impl()->changeSignalingState(newSignalingState);
@@ -162,9 +169,8 @@ void PeerConnection::setLocalDescription(Description::Type type, LocalDescriptio
 
 
 void PeerConnection::gatherLocalCandidates(std::vector<IceServer> additionalIceServers) {
 void PeerConnection::gatherLocalCandidates(std::vector<IceServer> additionalIceServers) {
 	auto iceTransport = impl()->getIceTransport();
 	auto iceTransport = impl()->getIceTransport();
-	if (!iceTransport) {
-		throw std::logic_error("No IceTransport. Local Description has not been set");
-	}
+	if (!iceTransport || !localDescription())
+		throw std::logic_error("Local description has not been set before gathering");
 
 
 	if (impl()->gatheringState == GatheringState::New) {
 	if (impl()->gatheringState == GatheringState::New) {
 		iceTransport->gatherLocalCandidates(impl()->localBundleMid(), additionalIceServers);
 		iceTransport->gatherLocalCandidates(impl()->localBundleMid(), additionalIceServers);
@@ -282,6 +288,26 @@ void PeerConnection::addRemoteCandidate(Candidate candidate) {
 	impl()->processRemoteCandidate(std::move(candidate));
 	impl()->processRemoteCandidate(std::move(candidate));
 }
 }
 
 
+Description PeerConnection::createOffer() {
+	auto iceTransport = impl()->initIceTransport();
+	if (!iceTransport)
+		throw std::runtime_error("Peer connection is closed");
+
+	Description desc = iceTransport->getLocalDescription(rtc::Description::Type::Offer);
+	impl()->populateLocalDescription(desc);
+	return desc;
+}
+
+Description PeerConnection::createAnswer() {
+	auto iceTransport = impl()->initIceTransport();
+	if (!iceTransport)
+		throw std::runtime_error("Peer connection is closed");
+
+	Description desc = iceTransport->getLocalDescription(rtc::Description::Type::Answer);
+	impl()->populateLocalDescription(desc);
+	return desc;
+}
+
 void PeerConnection::setMediaHandler(shared_ptr<MediaHandler> handler) {
 void PeerConnection::setMediaHandler(shared_ptr<MediaHandler> handler) {
 	impl()->setMediaHandler(std::move(handler));
 	impl()->setMediaHandler(std::move(handler));
 };
 };

+ 18 - 0
test/capi_track.cpp

@@ -19,6 +19,8 @@ static void sleep(unsigned int secs) { Sleep(secs * 1000); }
 #include <unistd.h> // for sleep
 #include <unistd.h> // for sleep
 #endif
 #endif
 
 
+#define BUFFER_SIZE 4096
+
 typedef struct {
 typedef struct {
 	rtcState state;
 	rtcState state;
 	rtcGatheringState gatheringState;
 	rtcGatheringState gatheringState;
@@ -187,9 +189,25 @@ int test_capi_track_main() {
 		goto error;
 		goto error;
 	}
 	}
 
 
+	// Test createOffer
+	char buffer[BUFFER_SIZE];
+	if (rtcCreateOffer(peer1->pc, buffer, BUFFER_SIZE) < 0) {
+		fprintf(stderr, "rtcCreateOffer failed\n");
+		goto error;
+	}
+	if (rtcGetLocalDescription(peer1->pc, buffer, BUFFER_SIZE) >= 0) {
+		fprintf(stderr, "rtcCreateOffer has set the local description\n");
+		goto error;
+	}
+
 	// Initiate the handshake
 	// Initiate the handshake
 	rtcSetLocalDescription(peer1->pc, NULL);
 	rtcSetLocalDescription(peer1->pc, NULL);
 
 
+	if (rtcGetLocalDescription(peer1->pc, buffer, BUFFER_SIZE) < 0) {
+		fprintf(stderr, "rtcGetLocalDescription failed\n");
+		goto error;
+	}
+
 	attempts = 10;
 	attempts = 10;
 	while ((!peer2->connected || !peer1->connected) && attempts--)
 	while ((!peer2->connected || !peer1->connected) && attempts--)
 		sleep(1);
 		sleep(1);