Browse Source

Merge pull request #161 from paullouisageneau/track-capi

Add Track C API
Paul-Louis Ageneau 4 years ago
parent
commit
7219b8e999
10 changed files with 393 additions and 75 deletions
  1. 2 1
      CMakeLists.txt
  2. 3 1
      include/rtc/description.hpp
  3. 13 8
      include/rtc/rtc.h
  4. 8 6
      src/description.cpp
  5. 0 3
      src/peerconnection.cpp
  6. 145 44
      src/rtc.cpp
  7. 4 1
      src/track.cpp
  8. 6 6
      test/capi_connectivity.cpp
  9. 198 0
      test/capi_track.cpp
  10. 14 5
      test/main.cpp

+ 2 - 1
CMakeLists.txt

@@ -91,8 +91,9 @@ set(LIBDATACHANNEL_HEADERS
 set(TESTS_SOURCES
 set(TESTS_SOURCES
     ${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/test/connectivity.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/test/connectivity.cpp
-    ${CMAKE_CURRENT_SOURCE_DIR}/test/capi.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/test/track.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/test/track.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/test/capi_connectivity.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/test/capi_track.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/test/websocket.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/test/websocket.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/test/benchmark.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/test/benchmark.cpp
 )
 )

+ 3 - 1
include/rtc/description.hpp

@@ -71,9 +71,11 @@ public:
 		Direction direction() const { return mDirection; }
 		Direction direction() const { return mDirection; }
 		void setDirection(Direction dir);
 		void setDirection(Direction dir);
 
 
-		virtual void parseSdpLine(string_view line);
+		operator string() const;
 		string generateSdp(string_view eol) const;
 		string generateSdp(string_view eol) const;
 
 
+		virtual void parseSdpLine(string_view line);
+
 	protected:
 	protected:
 		Entry(const string &mline, string mid, Direction dir = Direction::Unknown);
 		Entry(const string &mline, string mid, Direction dir = Direction::Unknown);
 		virtual string generateSdpLines(string_view eol) const;
 		virtual string generateSdpLines(string_view eol) const;

+ 13 - 8
include/rtc/rtc.h

@@ -29,10 +29,6 @@ extern "C" {
 #define RTC_EXPORT
 #define RTC_EXPORT
 #endif
 #endif
 
 
-#ifndef RTC_ENABLE_MEDIA
-#define RTC_ENABLE_MEDIA 1
-#endif
-
 #ifndef RTC_ENABLE_WEBSOCKET
 #ifndef RTC_ENABLE_WEBSOCKET
 #define RTC_ENABLE_WEBSOCKET 1
 #define RTC_ENABLE_WEBSOCKET 1
 #endif
 #endif
@@ -86,11 +82,12 @@ typedef struct {
 } rtcReliability;
 } rtcReliability;
 
 
 typedef void (*rtcLogCallbackFunc)(rtcLogLevel level, const char *message);
 typedef void (*rtcLogCallbackFunc)(rtcLogLevel level, const char *message);
-typedef void (*rtcDataChannelCallbackFunc)(int dc, void *ptr);
 typedef void (*rtcDescriptionCallbackFunc)(const char *sdp, const char *type, void *ptr);
 typedef void (*rtcDescriptionCallbackFunc)(const char *sdp, const char *type, void *ptr);
 typedef void (*rtcCandidateCallbackFunc)(const char *cand, const char *mid, void *ptr);
 typedef void (*rtcCandidateCallbackFunc)(const char *cand, const char *mid, void *ptr);
 typedef void (*rtcStateChangeCallbackFunc)(rtcState state, void *ptr);
 typedef void (*rtcStateChangeCallbackFunc)(rtcState state, void *ptr);
 typedef void (*rtcGatheringStateCallbackFunc)(rtcGatheringState state, void *ptr);
 typedef void (*rtcGatheringStateCallbackFunc)(rtcGatheringState state, void *ptr);
+typedef void (*rtcDataChannelCallbackFunc)(int dc, void *ptr);
+typedef void (*rtcTrackCallbackFunc)(int tr, void *ptr);
 typedef void (*rtcOpenCallbackFunc)(void *ptr);
 typedef void (*rtcOpenCallbackFunc)(void *ptr);
 typedef void (*rtcClosedCallbackFunc)(void *ptr);
 typedef void (*rtcClosedCallbackFunc)(void *ptr);
 typedef void (*rtcErrorCallbackFunc)(const char *error, void *ptr);
 typedef void (*rtcErrorCallbackFunc)(const char *error, void *ptr);
@@ -108,12 +105,12 @@ RTC_EXPORT void rtcSetUserPointer(int id, void *ptr);
 RTC_EXPORT int rtcCreatePeerConnection(const rtcConfiguration *config); // returns pc id
 RTC_EXPORT int rtcCreatePeerConnection(const rtcConfiguration *config); // returns pc id
 RTC_EXPORT int rtcDeletePeerConnection(int pc);
 RTC_EXPORT int rtcDeletePeerConnection(int pc);
 
 
-RTC_EXPORT int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb);
 RTC_EXPORT int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb);
 RTC_EXPORT int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb);
 RTC_EXPORT int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb);
 RTC_EXPORT int rtcSetLocalCandidateCallback(int pc, rtcCandidateCallbackFunc cb);
 RTC_EXPORT int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb);
 RTC_EXPORT int rtcSetStateChangeCallback(int pc, rtcStateChangeCallbackFunc cb);
 RTC_EXPORT int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb);
 RTC_EXPORT int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb);
 
 
+RTC_EXPORT int rtcSetLocalDescription(int pc);
 RTC_EXPORT int rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
 RTC_EXPORT int rtcSetRemoteDescription(int pc, const char *sdp, const char *type);
 RTC_EXPORT int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid);
 RTC_EXPORT int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid);
 
 
@@ -121,6 +118,7 @@ RTC_EXPORT int rtcGetLocalAddress(int pc, char *buffer, int size);
 RTC_EXPORT int rtcGetRemoteAddress(int pc, char *buffer, int size);
 RTC_EXPORT int rtcGetRemoteAddress(int pc, char *buffer, int size);
 
 
 // DataChannel
 // DataChannel
+RTC_EXPORT int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb);
 RTC_EXPORT int rtcCreateDataChannel(int pc, const char *label); // returns dc id
 RTC_EXPORT int rtcCreateDataChannel(int pc, const char *label); // returns dc id
 RTC_EXPORT int rtcCreateDataChannelExt(int pc, const char *label, const char *protocol,
 RTC_EXPORT int rtcCreateDataChannelExt(int pc, const char *label, const char *protocol,
                                        const rtcReliability *reliability); // returns dc id
                                        const rtcReliability *reliability); // returns dc id
@@ -130,6 +128,13 @@ RTC_EXPORT int rtcGetDataChannelLabel(int dc, char *buffer, int size);
 RTC_EXPORT int rtcGetDataChannelProtocol(int dc, char *buffer, int size);
 RTC_EXPORT int rtcGetDataChannelProtocol(int dc, char *buffer, int size);
 RTC_EXPORT int rtcGetDataChannelReliability(int dc, rtcReliability *reliability);
 RTC_EXPORT int rtcGetDataChannelReliability(int dc, rtcReliability *reliability);
 
 
+// Track
+RTC_EXPORT int rtcSetTrackCallback(int pc, rtcTrackCallbackFunc cb);
+RTC_EXPORT int rtcCreateTrack(int pc, const char *mediaDescriptionSdp); // returns tr id
+RTC_EXPORT int rtcDeleteTrack(int tr);
+
+RTC_EXPORT int rtcGetTrackDescription(int tr, char *buffer, int size);
+
 // WebSocket
 // WebSocket
 #if RTC_ENABLE_WEBSOCKET
 #if RTC_ENABLE_WEBSOCKET
 typedef struct {
 typedef struct {
@@ -141,7 +146,7 @@ RTC_EXPORT int rtcCreateWebSocketEx(const char *url, const rtcWsConfiguration *c
 RTC_EXPORT int rtcDeleteWebsocket(int ws);
 RTC_EXPORT int rtcDeleteWebsocket(int ws);
 #endif
 #endif
 
 
-// DataChannel and WebSocket common API
+// DataChannel, Track, and WebSocket common API
 RTC_EXPORT int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb);
 RTC_EXPORT int rtcSetOpenCallback(int id, rtcOpenCallbackFunc cb);
 RTC_EXPORT int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb);
 RTC_EXPORT int rtcSetClosedCallback(int id, rtcClosedCallbackFunc cb);
 RTC_EXPORT int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb);
 RTC_EXPORT int rtcSetErrorCallback(int id, rtcErrorCallbackFunc cb);
@@ -152,7 +157,7 @@ RTC_EXPORT int rtcGetBufferedAmount(int id); // total size buffered to send
 RTC_EXPORT int rtcSetBufferedAmountLowThreshold(int id, int amount);
 RTC_EXPORT int rtcSetBufferedAmountLowThreshold(int id, int amount);
 RTC_EXPORT int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb);
 RTC_EXPORT int rtcSetBufferedAmountLowCallback(int id, rtcBufferedAmountLowCallbackFunc cb);
 
 
-// DataChannel and WebSocket common extended API
+// DataChannel, Track, and WebSocket common extended API
 RTC_EXPORT int rtcGetAvailableAmount(int id); // total size available to receive
 RTC_EXPORT int rtcGetAvailableAmount(int id); // total size available to receive
 RTC_EXPORT int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb);
 RTC_EXPORT int rtcSetAvailableCallback(int id, rtcAvailableCallbackFunc cb);
 RTC_EXPORT int rtcReceiveMessage(int id, char *buffer, int *size);
 RTC_EXPORT int rtcReceiveMessage(int id, char *buffer, int *size);

+ 8 - 6
src/description.cpp

@@ -378,6 +378,8 @@ Description::Entry::Entry(const string &mline, string mid, Direction dir)
 
 
 void Description::Entry::setDirection(Direction dir) { mDirection = dir; }
 void Description::Entry::setDirection(Direction dir) { mDirection = dir; }
 
 
+Description::Entry::operator string() const { return generateSdp("\r\n"); }
+
 string Description::Entry::generateSdp(string_view eol) const {
 string Description::Entry::generateSdp(string_view eol) const {
 	std::ostringstream sdp;
 	std::ostringstream sdp;
 	// Port 9 is the discard protocol
 	// Port 9 is the discard protocol
@@ -432,7 +434,9 @@ void Description::Entry::parseSdpLine(string_view line) {
 			mDirection = Direction::SendRecv;
 			mDirection = Direction::SendRecv;
 		else if (key == "inactive")
 		else if (key == "inactive")
 			mDirection = Direction::Inactive;
 			mDirection = Direction::Inactive;
-		else
+		else if (key == "bundle-only") {
+			// always added
+		} else
 			mAttributes.emplace_back(line.substr(2));
 			mAttributes.emplace_back(line.substr(2));
 	}
 	}
 }
 }
@@ -492,15 +496,10 @@ Description::Media::Media(const string &sdp) : Entry(sdp, "", Direction::Unknown
 
 
 	if (mid().empty())
 	if (mid().empty())
 		throw std::invalid_argument("Missing mid in media SDP");
 		throw std::invalid_argument("Missing mid in media SDP");
-
-	if (std::find(mAttributes.begin(), mAttributes.end(), "rtcp-mux") != mAttributes.end())
-		mAttributes.emplace_back("rtcp-mux");
 }
 }
 
 
 Description::Media::Media(const string &mline, string mid, Direction dir)
 Description::Media::Media(const string &mline, string mid, Direction dir)
     : Entry(mline, std::move(mid), dir) {
     : Entry(mline, std::move(mid), dir) {
-
-	mAttributes.emplace_back("rtcp-mux");
 }
 }
 
 
 string Description::Media::description() const {
 string Description::Media::description() const {
@@ -612,6 +611,7 @@ string Description::Media::generateSdpLines(string_view eol) const {
 		sdp << "b=AS:" << mBas << eol;
 		sdp << "b=AS:" << mBas << eol;
 
 
 	sdp << Entry::generateSdpLines(eol);
 	sdp << Entry::generateSdpLines(eol);
+	sdp << "a=rtcp-mux" << eol;
 
 
 	for (auto it = mRtpMap.begin(); it != mRtpMap.end(); ++it) {
 	for (auto it = mRtpMap.begin(); it != mRtpMap.end(); ++it) {
 		auto &map = it->second;
 		auto &map = it->second;
@@ -658,6 +658,8 @@ void Description::Media::parseSdpLine(string_view line) {
 			} else {
 			} else {
 				it->second.fmtps.emplace_back(value.substr(p + 1));
 				it->second.fmtps.emplace_back(value.substr(p + 1));
 			}
 			}
+		} else if (key == "rtcp-mux") {
+			// always added
 		} else {
 		} else {
 			Entry::parseSdpLine(line);
 			Entry::parseSdpLine(line);
 		}
 		}

+ 0 - 3
src/peerconnection.cpp

@@ -685,9 +685,6 @@ void PeerConnection::incomingTrack(Description::Media description) {
 
 
 void PeerConnection::openTracks() {
 void PeerConnection::openTracks() {
 #if RTC_ENABLE_MEDIA
 #if RTC_ENABLE_MEDIA
-	if (!hasMedia())
-		return;
-
 	if (auto transport = std::atomic_load(&mDtlsTransport)) {
 	if (auto transport = std::atomic_load(&mDtlsTransport)) {
 		auto srtpTransport = std::reinterpret_pointer_cast<DtlsSrtpTransport>(transport);
 		auto srtpTransport = std::reinterpret_pointer_cast<DtlsSrtpTransport>(transport);
 		std::shared_lock lock(mTracksMutex); // read-only
 		std::shared_lock lock(mTracksMutex); // read-only

+ 145 - 44
src/rtc.cpp

@@ -50,6 +50,7 @@ namespace {
 
 
 std::unordered_map<int, shared_ptr<PeerConnection>> peerConnectionMap;
 std::unordered_map<int, shared_ptr<PeerConnection>> peerConnectionMap;
 std::unordered_map<int, shared_ptr<DataChannel>> dataChannelMap;
 std::unordered_map<int, shared_ptr<DataChannel>> dataChannelMap;
+std::unordered_map<int, shared_ptr<Track>> trackMap;
 #if RTC_ENABLE_WEBSOCKET
 #if RTC_ENABLE_WEBSOCKET
 std::unordered_map<int, shared_ptr<WebSocket>> webSocketMap;
 std::unordered_map<int, shared_ptr<WebSocket>> webSocketMap;
 #endif
 #endif
@@ -84,6 +85,14 @@ shared_ptr<DataChannel> getDataChannel(int id) {
 		throw std::invalid_argument("DataChannel ID does not exist");
 		throw std::invalid_argument("DataChannel ID does not exist");
 }
 }
 
 
+shared_ptr<Track> 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<PeerConnection> ptr) {
 int emplacePeerConnection(shared_ptr<PeerConnection> ptr) {
 	std::lock_guard lock(mutex);
 	std::lock_guard lock(mutex);
 	int pc = ++lastId;
 	int pc = ++lastId;
@@ -100,6 +109,14 @@ int emplaceDataChannel(shared_ptr<DataChannel> ptr) {
 	return dc;
 	return dc;
 }
 }
 
 
+int emplaceTrack(shared_ptr<Track> 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) {
 void erasePeerConnection(int pc) {
 	std::lock_guard lock(mutex);
 	std::lock_guard lock(mutex);
 	if (peerConnectionMap.erase(pc) == 0)
 	if (peerConnectionMap.erase(pc) == 0)
@@ -114,6 +131,13 @@ void eraseDataChannel(int dc) {
 	userPointerMap.erase(dc);
 	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");
+	userPointerMap.erase(tr);
+}
+
 #if RTC_ENABLE_WEBSOCKET
 #if RTC_ENABLE_WEBSOCKET
 shared_ptr<WebSocket> getWebSocket(int id) {
 shared_ptr<WebSocket> getWebSocket(int id) {
 	std::lock_guard lock(mutex);
 	std::lock_guard lock(mutex);
@@ -143,11 +167,13 @@ shared_ptr<Channel> getChannel(int id) {
 	std::lock_guard lock(mutex);
 	std::lock_guard lock(mutex);
 	if (auto it = dataChannelMap.find(id); it != dataChannelMap.end())
 	if (auto it = dataChannelMap.find(id); it != dataChannelMap.end())
 		return it->second;
 		return it->second;
+	if (auto it = trackMap.find(id); it != trackMap.end())
+		return it->second;
 #if RTC_ENABLE_WEBSOCKET
 #if RTC_ENABLE_WEBSOCKET
 	if (auto it = webSocketMap.find(id); it != webSocketMap.end())
 	if (auto it = webSocketMap.find(id); it != webSocketMap.end())
 		return it->second;
 		return it->second;
 #endif
 #endif
-	throw std::invalid_argument("DataChannel or WebSocket ID does not exist");
+	throw std::invalid_argument("DataChannel, Track, or WebSocket ID does not exist");
 }
 }
 
 
 template <typename F> int wrap(F func) {
 template <typename F> int wrap(F func) {
@@ -287,6 +313,51 @@ int rtcDeleteDataChannel(int dc) {
 	});
 	});
 }
 }
 
 
+int rtcCreateTrack(int pc, const char *mediaDescriptionSdp) {
+	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->createTrack(std::move(media)));
+	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);
+	});
+}
+
+int rtcGetTrackDescription(int tr, char *buffer, int size) {
+	return WRAP({
+		auto track = getTrack(tr);
+
+		if (size <= 0)
+			return 0;
+
+		if (!buffer)
+			throw std::invalid_argument("Unexpected null pointer for buffer");
+
+		string description(track->description());
+		const char *data = description.data();
+		size = std::min(size - 1, int(description.size()));
+		std::copy(data, data + size, buffer);
+		buffer[size] = '\0';
+		return int(size + 1);
+	});
+}
+
 #if RTC_ENABLE_WEBSOCKET
 #if RTC_ENABLE_WEBSOCKET
 int rtcCreateWebSocket(const char *url) {
 int rtcCreateWebSocket(const char *url) {
 	return WRAP({
 	return WRAP({
@@ -321,22 +392,6 @@ int rtcDeleteWebsocket(int ws) {
 }
 }
 #endif
 #endif
 
 
-int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb) {
-	return WRAP({
-		auto peerConnection = getPeerConnection(pc);
-		if (cb)
-			peerConnection->onDataChannel([pc, cb](std::shared_ptr<DataChannel> dataChannel) {
-				int dc = emplaceDataChannel(dataChannel);
-				if (auto ptr = getUserPointer(pc)) {
-					rtcSetUserPointer(dc, *ptr);
-					cb(dc, *ptr);
-				}
-			});
-		else
-			peerConnection->onDataChannel(nullptr);
-	});
-}
-
 int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb) {
 int rtcSetLocalDescriptionCallback(int pc, rtcDescriptionCallbackFunc cb) {
 	return WRAP({
 	return WRAP({
 		auto peerConnection = getPeerConnection(pc);
 		auto peerConnection = getPeerConnection(pc);
@@ -389,12 +444,51 @@ int rtcSetGatheringStateChangeCallback(int pc, rtcGatheringStateCallbackFunc cb)
 	});
 	});
 }
 }
 
 
+int rtcSetDataChannelCallback(int pc, rtcDataChannelCallbackFunc cb) {
+	return WRAP({
+		auto peerConnection = getPeerConnection(pc);
+		if (cb)
+			peerConnection->onDataChannel([pc, cb](std::shared_ptr<DataChannel> dataChannel) {
+				int dc = emplaceDataChannel(dataChannel);
+				if (auto ptr = getUserPointer(pc)) {
+					rtcSetUserPointer(dc, *ptr);
+					cb(dc, *ptr);
+				}
+			});
+		else
+			peerConnection->onDataChannel(nullptr);
+	});
+}
+
+int rtcSetTrackCallback(int pc, rtcTrackCallbackFunc cb) {
+	return WRAP({
+		auto peerConnection = getPeerConnection(pc);
+		if (cb)
+			peerConnection->onTrack([pc, cb](std::shared_ptr<Track> track) {
+				int tr = emplaceTrack(track);
+				if (auto ptr = getUserPointer(pc)) {
+					rtcSetUserPointer(tr, *ptr);
+					cb(tr, *ptr);
+				}
+			});
+		else
+			peerConnection->onTrack(nullptr);
+	});
+}
+
+int rtcSetLocalDescription(int pc) {
+	return WRAP({
+		auto peerConnection = getPeerConnection(pc);
+		peerConnection->setLocalDescription();
+	});
+}
+
 int rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
 int rtcSetRemoteDescription(int pc, const char *sdp, const char *type) {
 	return WRAP({
 	return WRAP({
 		auto peerConnection = getPeerConnection(pc);
 		auto peerConnection = getPeerConnection(pc);
 
 
 		if (!sdp)
 		if (!sdp)
-			throw std::invalid_argument("Unexpected null pointer");
+			throw std::invalid_argument("Unexpected null pointer for remote description");
 
 
 		peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""});
 		peerConnection->setRemoteDescription({string(sdp), type ? string(type) : ""});
 	});
 	});
@@ -405,7 +499,7 @@ int rtcAddRemoteCandidate(int pc, const char *cand, const char *mid) {
 		auto peerConnection = getPeerConnection(pc);
 		auto peerConnection = getPeerConnection(pc);
 
 
 		if (!cand)
 		if (!cand)
-			throw std::invalid_argument("Unexpected null pointer");
+			throw std::invalid_argument("Unexpected null pointer for remote candidate");
 
 
 		peerConnection->addRemoteCandidate({string(cand), mid ? string(mid) : ""});
 		peerConnection->addRemoteCandidate({string(cand), mid ? string(mid) : ""});
 	});
 	});
@@ -415,12 +509,12 @@ int rtcGetLocalAddress(int pc, char *buffer, int size) {
 	return WRAP({
 	return WRAP({
 		auto peerConnection = getPeerConnection(pc);
 		auto peerConnection = getPeerConnection(pc);
 
 
-		if (!buffer)
-			throw std::invalid_argument("Unexpected null pointer");
-
 		if (size <= 0)
 		if (size <= 0)
 			return 0;
 			return 0;
 
 
+		if (!buffer)
+			throw std::invalid_argument("Unexpected null pointer for buffer");
+
 		if (auto addr = peerConnection->localAddress()) {
 		if (auto addr = peerConnection->localAddress()) {
 			const char *data = addr->data();
 			const char *data = addr->data();
 			size = std::min(size - 1, int(addr->size()));
 			size = std::min(size - 1, int(addr->size()));
@@ -435,12 +529,12 @@ int rtcGetRemoteAddress(int pc, char *buffer, int size) {
 	return WRAP({
 	return WRAP({
 		auto peerConnection = getPeerConnection(pc);
 		auto peerConnection = getPeerConnection(pc);
 
 
-		if (!buffer)
-			throw std::invalid_argument("Unexpected null pointer");
-
 		if (size <= 0)
 		if (size <= 0)
 			return 0;
 			return 0;
 
 
+		if (!buffer)
+			throw std::invalid_argument("Unexpected null pointer for buffer");
+
 		if (auto addr = peerConnection->remoteAddress()) {
 		if (auto addr = peerConnection->remoteAddress()) {
 			const char *data = addr->data();
 			const char *data = addr->data();
 			size = std::min(size - 1, int(addr->size()));
 			size = std::min(size - 1, int(addr->size()));
@@ -455,12 +549,12 @@ int rtcGetDataChannelLabel(int dc, char *buffer, int size) {
 	return WRAP({
 	return WRAP({
 		auto dataChannel = getDataChannel(dc);
 		auto dataChannel = getDataChannel(dc);
 
 
-		if (!buffer)
-			throw std::invalid_argument("Unexpected null pointer");
-
 		if (size <= 0)
 		if (size <= 0)
 			return 0;
 			return 0;
 
 
+		if (!buffer)
+			throw std::invalid_argument("Unexpected null pointer for buffer");
+
 		string label = dataChannel->label();
 		string label = dataChannel->label();
 		const char *data = label.data();
 		const char *data = label.data();
 		size = std::min(size - 1, int(label.size()));
 		size = std::min(size - 1, int(label.size()));
@@ -474,12 +568,12 @@ int rtcGetDataChannelProtocol(int dc, char *buffer, int size) {
 	return WRAP({
 	return WRAP({
 		auto dataChannel = getDataChannel(dc);
 		auto dataChannel = getDataChannel(dc);
 
 
-		if (!buffer)
-			throw std::invalid_argument("Unexpected null pointer");
-
 		if (size <= 0)
 		if (size <= 0)
 			return 0;
 			return 0;
 
 
+		if (!buffer)
+			throw std::invalid_argument("Unexpected null pointer for buffer");
+
 		string protocol = dataChannel->protocol();
 		string protocol = dataChannel->protocol();
 		const char *data = protocol.data();
 		const char *data = protocol.data();
 		size = std::min(size - 1, int(protocol.size()));
 		size = std::min(size - 1, int(protocol.size()));
@@ -494,7 +588,7 @@ int rtcGetDataChannelReliability(int dc, rtcReliability *reliability) {
 		auto dataChannel = getDataChannel(dc);
 		auto dataChannel = getDataChannel(dc);
 
 
 		if (!reliability)
 		if (!reliability)
-			throw std::invalid_argument("Unexpected null pointer");
+			throw std::invalid_argument("Unexpected null pointer for reliability");
 
 
 		Reliability r = dataChannel->reliability();
 		Reliability r = dataChannel->reliability();
 		std::memset(reliability, 0, sizeof(*reliability));
 		std::memset(reliability, 0, sizeof(*reliability));
@@ -573,8 +667,8 @@ int rtcSendMessage(int id, const char *data, int size) {
 	return WRAP({
 	return WRAP({
 		auto channel = getChannel(id);
 		auto channel = getChannel(id);
 
 
-		if (!data)
-			throw std::invalid_argument("Unexpected null pointer");
+		if (!data && size != 0)
+			throw std::invalid_argument("Unexpected null pointer for data");
 
 
 		if (size >= 0) {
 		if (size >= 0) {
 			auto b = reinterpret_cast<const byte *>(data);
 			auto b = reinterpret_cast<const byte *>(data);
@@ -637,25 +731,32 @@ int rtcReceiveMessage(int id, char *buffer, int *size) {
 	return WRAP({
 	return WRAP({
 		auto channel = getChannel(id);
 		auto channel = getChannel(id);
 
 
-		if (!buffer || !size)
-			throw std::invalid_argument("Unexpected null pointer");
+		if (!size)
+			throw std::invalid_argument("Unexpected null pointer for size");
+
+		if (!buffer && *size != 0)
+			throw std::invalid_argument("Unexpected null pointer for buffer");
 
 
 		if (auto message = channel->receive())
 		if (auto message = channel->receive())
 			return std::visit( //
 			return std::visit( //
 			    overloaded{    //
 			    overloaded{    //
 			               [&](binary b) {
 			               [&](binary b) {
-				               *size = std::min(*size, int(b.size()));
-				               auto data = reinterpret_cast<const char *>(b.data());
-				               std::copy(data, data + *size, buffer);
+				               if (*size > 0) {
+					               *size = std::min(*size, int(b.size()));
+					               auto data = reinterpret_cast<const char *>(b.data());
+					               std::copy(data, data + *size, buffer);
+				               }
 				               return 1;
 				               return 1;
 			               },
 			               },
 			               [&](string s) {
 			               [&](string s) {
-				               int len = std::min(*size - 1, int(s.size()));
-				               if (len >= 0) {
-					               std::copy(s.data(), s.data() + len, buffer);
-					               buffer[len] = '\0';
+				               if (*size > 0) {
+					               int len = std::min(*size - 1, int(s.size()));
+					               if (len >= 0) {
+						               std::copy(s.data(), s.data() + len, buffer);
+						               buffer[len] = '\0';
+					               }
+					               *size = -(len + 1);
 				               }
 				               }
-				               *size = -(len + 1);
 				               return 1;
 				               return 1;
 			               }},
 			               }},
 			    *message);
 			    *message);

+ 4 - 1
src/track.cpp

@@ -70,7 +70,10 @@ size_t Track::availableAmount() const {
 }
 }
 
 
 #if RTC_ENABLE_MEDIA
 #if RTC_ENABLE_MEDIA
-void Track::open(shared_ptr<DtlsSrtpTransport> transport) { mDtlsSrtpTransport = transport; }
+void Track::open(shared_ptr<DtlsSrtpTransport> transport) {
+	mDtlsSrtpTransport = transport;
+	triggerOpen();
+}
 #endif
 #endif
 
 
 bool Track::outgoing(message_ptr message) {
 bool Track::outgoing(message_ptr message) {

+ 6 - 6
test/capi.cpp → test/capi_connectivity.cpp

@@ -37,8 +37,8 @@ typedef struct {
 	bool connected;
 	bool connected;
 } Peer;
 } Peer;
 
 
-Peer *peer1 = NULL;
-Peer *peer2 = NULL;
+static Peer *peer1 = NULL;
+static Peer *peer2 = NULL;
 
 
 static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
 static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
 	Peer *peer = (Peer *)ptr;
 	Peer *peer = (Peer *)ptr;
@@ -132,7 +132,7 @@ static void deletePeer(Peer *peer) {
 	}
 	}
 }
 }
 
 
-int test_capi_main() {
+int test_capi_connectivity_main() {
 	int attempts;
 	int attempts;
 
 
 	rtcInitLogger(RTC_LOG_DEBUG, nullptr);
 	rtcInitLogger(RTC_LOG_DEBUG, nullptr);
@@ -170,7 +170,7 @@ int test_capi_main() {
 	rtcSetMessageCallback(peer1->dc, messageCallback);
 	rtcSetMessageCallback(peer1->dc, messageCallback);
 
 
 	attempts = 10;
 	attempts = 10;
-	while (!peer2->connected && !peer1->connected && attempts--)
+	while ((!peer2->connected || !peer1->connected) && attempts--)
 		sleep(1);
 		sleep(1);
 
 
 	if (peer1->state != RTC_CONNECTED || peer2->state != RTC_CONNECTED) {
 	if (peer1->state != RTC_CONNECTED || peer2->state != RTC_CONNECTED) {
@@ -213,7 +213,7 @@ error:
 
 
 #include <stdexcept>
 #include <stdexcept>
 
 
-void test_capi() {
-	if (test_capi_main())
+void test_capi_connectivity() {
+	if (test_capi_connectivity_main())
 		throw std::runtime_error("Connection failed");
 		throw std::runtime_error("Connection failed");
 }
 }

+ 198 - 0
test/capi_track.cpp

@@ -0,0 +1,198 @@
+/**
+ * Copyright (c) 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/rtc.h>
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+#ifdef _WIN32
+#include <windows.h>
+static void sleep(unsigned int secs) { Sleep(secs * 1000); }
+#else
+#include <unistd.h> // for sleep
+#endif
+
+typedef struct {
+	rtcState state;
+	rtcGatheringState gatheringState;
+	int pc;
+	int tr;
+	bool connected;
+} Peer;
+
+static Peer *peer1 = NULL;
+static Peer *peer2 = NULL;
+
+static const char *mediaDescription = "video 9 UDP/TLS/RTP/SAVPF\r\n"
+                                      "a=mid:video\r\n";
+
+static void descriptionCallback(const char *sdp, const char *type, void *ptr) {
+	Peer *peer = (Peer *)ptr;
+	printf("Description %d:\n%s\n", peer == peer1 ? 1 : 2, sdp);
+	Peer *other = peer == peer1 ? peer2 : peer1;
+	rtcSetRemoteDescription(other->pc, sdp, type);
+}
+
+static void candidateCallback(const char *cand, const char *mid, void *ptr) {
+	Peer *peer = (Peer *)ptr;
+	printf("Candidate %d: %s\n", peer == peer1 ? 1 : 2, cand);
+	Peer *other = peer == peer1 ? peer2 : peer1;
+	rtcAddRemoteCandidate(other->pc, cand, mid);
+}
+
+static void stateChangeCallback(rtcState state, void *ptr) {
+	Peer *peer = (Peer *)ptr;
+	peer->state = state;
+	printf("State %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
+}
+
+static void gatheringStateCallback(rtcGatheringState state, void *ptr) {
+	Peer *peer = (Peer *)ptr;
+	peer->gatheringState = state;
+	printf("Gathering state %d: %d\n", peer == peer1 ? 1 : 2, (int)state);
+}
+
+static void openCallback(void *ptr) {
+	Peer *peer = (Peer *)ptr;
+	peer->connected = true;
+	printf("Track %d: Open\n", peer == peer1 ? 1 : 2);
+}
+
+static void closedCallback(void *ptr) {
+	Peer *peer = (Peer *)ptr;
+	peer->connected = false;
+}
+
+static void trackCallback(int tr, void *ptr) {
+	Peer *peer = (Peer *)ptr;
+	peer->tr = tr;
+	peer->connected = true;
+	rtcSetClosedCallback(tr, closedCallback);
+
+	char buffer[1024];
+	if (rtcGetTrackDescription(tr, buffer, 1024) >= 0)
+		printf("Track %d: Received with media description: \n%s\n", peer == peer1 ? 1 : 2, buffer);
+}
+
+static Peer *createPeer(const rtcConfiguration *config) {
+	Peer *peer = (Peer *)malloc(sizeof(Peer));
+	if (!peer)
+		return nullptr;
+	memset(peer, 0, sizeof(Peer));
+
+	// Create peer connection
+	peer->pc = rtcCreatePeerConnection(config);
+	rtcSetUserPointer(peer->pc, peer);
+	rtcSetTrackCallback(peer->pc, trackCallback);
+	rtcSetLocalDescriptionCallback(peer->pc, descriptionCallback);
+	rtcSetLocalCandidateCallback(peer->pc, candidateCallback);
+	rtcSetStateChangeCallback(peer->pc, stateChangeCallback);
+	rtcSetGatheringStateChangeCallback(peer->pc, gatheringStateCallback);
+
+	return peer;
+}
+
+static void deletePeer(Peer *peer) {
+	if (peer) {
+		if (peer->tr)
+			rtcDeleteTrack(peer->tr);
+		if (peer->pc)
+			rtcDeletePeerConnection(peer->pc);
+		free(peer);
+	}
+}
+
+int test_capi_track_main() {
+	int attempts;
+
+	rtcInitLogger(RTC_LOG_DEBUG, nullptr);
+
+	// Create peer 1
+	rtcConfiguration config1;
+	memset(&config1, 0, sizeof(config1));
+	// STUN server example
+	// const char *iceServers[1] = {"stun:stun.l.google.com:19302"};
+	// config1.iceServers = iceServers;
+	// config1.iceServersCount = 1;
+
+	peer1 = createPeer(&config1);
+	if (!peer1)
+		goto error;
+
+	// Create peer 2
+	rtcConfiguration config2;
+	memset(&config2, 0, sizeof(config2));
+	// STUN server example
+	// config2.iceServers = iceServers;
+	// config2.iceServersCount = 1;
+	// Port range example
+	config2.portRangeBegin = 5000;
+	config2.portRangeEnd = 6000;
+
+	peer2 = createPeer(&config2);
+	if (!peer2)
+		goto error;
+
+	// Peer 1: Create track
+	peer1->tr = rtcCreateTrack(peer1->pc, mediaDescription);
+	rtcSetOpenCallback(peer1->tr, openCallback);
+	rtcSetClosedCallback(peer1->tr, closedCallback);
+
+	// Initiate the handshake
+	rtcSetLocalDescription(peer1->pc);
+
+	attempts = 10;
+	while ((!peer2->connected || !peer1->connected) && attempts--)
+		sleep(1);
+
+	if (peer1->state != RTC_CONNECTED || peer2->state != RTC_CONNECTED) {
+		fprintf(stderr, "PeerConnection is not connected\n");
+		goto error;
+	}
+
+	if (!peer1->connected || !peer2->connected) {
+		fprintf(stderr, "Track is not connected\n");
+		goto error;
+	}
+
+	deletePeer(peer1);
+	sleep(1);
+	deletePeer(peer2);
+	sleep(1);
+
+	// You may call rtcCleanup() when finished to free static resources
+	rtcCleanup();
+	sleep(2);
+
+	printf("Success\n");
+	return 0;
+
+error:
+	deletePeer(peer1);
+	deletePeer(peer2);
+	return -1;
+}
+
+#include <stdexcept>
+
+void test_capi_track() {
+	if (test_capi_track_main())
+		throw std::runtime_error("Connection failed");
+}

+ 14 - 5
test/main.cpp

@@ -24,8 +24,9 @@ using namespace std;
 using namespace chrono_literals;
 using namespace chrono_literals;
 
 
 void test_connectivity();
 void test_connectivity();
-void test_capi();
 void test_track();
 void test_track();
+void test_capi_connectivity();
+void test_capi_track();
 void test_websocket();
 void test_websocket();
 size_t benchmark(chrono::milliseconds duration);
 size_t benchmark(chrono::milliseconds duration);
 
 
@@ -50,11 +51,11 @@ int main(int argc, char **argv) {
 		return -1;
 		return -1;
 	}
 	}
 	try {
 	try {
-		cout << endl << "*** Running WebRTC C API test..." << endl;
-		test_capi();
-		cout << "*** Finished WebRTC C API test" << endl;
+		cout << endl << "*** Running WebRTC C API connectivity test..." << endl;
+		test_capi_connectivity();
+		cout << "*** Finished WebRTC C API connectivity test" << endl;
 	} catch (const exception &e) {
 	} catch (const exception &e) {
-		cerr << "WebRTC C API test failed: " << e.what() << endl;
+		cerr << "WebRTC C API connectivity test failed: " << e.what() << endl;
 		return -1;
 		return -1;
 	}
 	}
 #if RTC_ENABLE_MEDIA
 #if RTC_ENABLE_MEDIA
@@ -66,6 +67,14 @@ int main(int argc, char **argv) {
 		cerr << "WebRTC Track test failed: " << e.what() << endl;
 		cerr << "WebRTC Track test failed: " << e.what() << endl;
 		return -1;
 		return -1;
 	}
 	}
+	try {
+		cout << endl << "*** Running WebRTC C API track test..." << endl;
+		test_capi_track();
+		cout << "*** Finished WebRTC C API track test" << endl;
+	} catch (const exception &e) {
+		cerr << "WebRTC C API track test failed: " << e.what() << endl;
+		return -1;
+	}
 #endif
 #endif
 #if RTC_ENABLE_WEBSOCKET
 #if RTC_ENABLE_WEBSOCKET
 	try {
 	try {