diff --git a/include/rtc/rtc.h b/include/rtc/rtc.h index e82bb0f..9fa2344 100644 --- a/include/rtc/rtc.h +++ b/include/rtc/rtc.h @@ -77,6 +77,28 @@ typedef enum { // Don't change, it must match plog severity RTC_LOG_VERBOSE = 6 } rtcLogLevel; +#if RTC_ENABLE_MEDIA + +typedef enum { + // video + RTC_CODEC_H264, + RTC_CODEC_VP8, + RTC_CODEC_VP9, + + // audio + RTC_CODEC_OPUS +} rtcCodec; + +typedef enum { + RTC_DIRECTION_UNKNOWN, + RTC_DIRECTION_SENDONLY, + RTC_DIRECTION_RECVONLY, + RTC_DIRECTION_SENDRECV, + RTC_DIRECTION_INACTIVE +} rtcDirection; + +#endif // RTC_ENABLE_MEDIA + #define RTC_ERR_SUCCESS 0 #define RTC_ERR_INVALID -1 // invalid argument #define RTC_ERR_FAILURE -2 // runtime error @@ -129,6 +151,7 @@ RTC_EXPORT void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb); // User pointer RTC_EXPORT void rtcSetUserPointer(int id, void *ptr); +RTC_EXPORT void * rtcGetUserPointer(int i); // PeerConnection RTC_EXPORT int rtcCreatePeerConnection(const rtcConfiguration *config); // returns pc id @@ -176,6 +199,90 @@ RTC_EXPORT int rtcDeleteTrack(int tr); RTC_EXPORT int rtcGetTrackDescription(int tr, char *buffer, int size); +// Media +#if RTC_ENABLE_MEDIA + +/// Add track +/// @param pc Peer connection id +/// @param codec Codec +/// @param payloadType Payload type +/// @param ssrc SSRC +/// @param _mid MID +/// @param _direction Direction +/// @param _name Name (optional) +/// @param _msid MSID (optional) +/// @returns Track id +RTC_EXPORT int rtcAddTrackEx(int pc, rtcCodec codec, int payloadType, uint32_t ssrc, const char *_mid, rtcDirection direction, const char *_name, const char *_msid); + +/// Set H264PacketizationHandler for track +/// @param tr Track id +/// @param ssrc SSRC +/// @param cname CName +/// @param payloadType Payload Type +/// @param clockRate Clock rate +/// @param _sequenceNumber Sequence number +/// @param _timestamp Timestamp +RTC_EXPORT int rtcSetH264PacketizationHandler(int tr, uint32_t ssrc, const char * cname, uint8_t payloadType, uint32_t clockRate, uint16_t _sequenceNumber, uint32_t _timestamp); + +/// Set OpusPacketizationHandler for track +/// @param tr Track id +/// @param ssrc SSRC +/// @param cname CName +/// @param payloadType Payload Type +/// @param clockRate Clock rate +/// @param _sequenceNumber Sequence number +/// @param _timestamp Timestamp +RTC_EXPORT int rtcSetOpusPacketizationHandler(int tr, uint32_t ssrc, const char * cname, uint8_t payloadType, uint32_t clockRate, uint16_t _sequenceNumber, uint32_t _timestamp); + +/// Set start time for RTP stream +/// @param startTime_s Start time in seconds +/// @param timeIntervalSince1970 Set true if `startTime_s` is time interval since 1970, false if `startTime_s` is time interval since 1900 +/// @param _timestamp Start timestamp +int rtcSetRtpConfigurationStartTime(int id, double startTime_s, bool timeIntervalSince1970, uint32_t _timestamp); + +/// Start stats recording for RTCP Sender Reporter +/// @param id Track identifier +int rtcStartRtcpSenderReporterRecording(int id); + + +/// Transform seconds to timestamp using track's clock rate +/// @param id Track id +/// @param seconds Seconds +/// @param timestamp Pointer to result +int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t * timestamp); + +/// Transform timestamp to seconds using track's clock rate +/// @param id Track id +/// @param timestamp Timestamp +/// @param seconds Pointer for result +int rtcTransformTimestampToSeconds(int id, uint32_t timestamp, double * seconds); + +/// Get current timestamp +/// @param id Track id +/// @param timestamp Pointer for result +int rtcGetCurrentTrackTimestamp(int id, uint32_t * timestamp); + +/// Get start timestamp for track identified by given id +/// @param id Track id +/// @param timestamp Pointer for result +int rtcGetTrackStartTimestamp(int id, uint32_t * timestamp); + +/// Set RTP timestamp for track identified by given id +/// @param id Track id +/// @param timestamp New timestamp +int rtcSetTrackRTPTimestamp(int id, uint32_t timestamp); + +/// Get timestamp of previous RTCP SR +/// @param id Track id +/// @param timestamp Pointer for result +int rtcGetPreviousTrackSenderReportTimestamp(int id, uint32_t * timestamp); + +/// Set `NeedsToReport` flag in RTCPSenderReportable handler identified by given track id +/// @param id Track id +int rtcSetNeedsToSendRTCPSR(int id); + +#endif // RTC_ENABLE_MEDIA + // WebSocket #if RTC_ENABLE_WEBSOCKET typedef struct { diff --git a/src/capi.cpp b/src/capi.cpp index 5b26076..de4898b 100644 --- a/src/capi.cpp +++ b/src/capi.cpp @@ -44,6 +44,7 @@ using namespace rtc; using std::shared_ptr; using std::string; +using std::optional; using std::chrono::milliseconds; namespace { @@ -51,6 +52,10 @@ namespace { std::unordered_map> peerConnectionMap; std::unordered_map> dataChannelMap; std::unordered_map> trackMap; +#if RTC_ENABLE_MEDIA +std::unordered_map> rtcpSenderMap; +std::unordered_map> rtpConfigMap; +#endif #if RTC_ENABLE_WEBSOCKET std::unordered_map> webSocketMap; #endif @@ -135,9 +140,66 @@ 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 + rtcpSenderMap.erase(tr); + rtpConfigMap.erase(tr); +#endif userPointerMap.erase(tr); } +#if RTC_ENABLE_MEDIA + +shared_ptr getRTCPSender(int id) { + std::lock_guard lock(mutex); + if (auto it = rtcpSenderMap.find(id); it != rtcpSenderMap.end()) + return it->second; + else + throw std::invalid_argument("RTCPSenderReportable ID does not exist"); +} + +void emplaceRTCPSender(shared_ptr ptr, int tr) { + std::lock_guard lock(mutex); + rtcpSenderMap.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("RTPConfiguration ID does not exist"); +} + +void emplaceRTPConfig(shared_ptr ptr, int tr) { + std::lock_guard lock(mutex); + rtpConfigMap.emplace(std::make_pair(tr, ptr)); +} + +Description::Direction rtcDirectionToDirection(rtcDirection direction) { + switch (direction) { + case RTC_DIRECTION_SENDONLY: + return Description::Direction::SendOnly; + case RTC_DIRECTION_RECVONLY: + return Description::Direction::RecvOnly; + case RTC_DIRECTION_SENDRECV: + return Description::Direction::SendRecv; + case RTC_DIRECTION_INACTIVE: + return Description::Direction::Inactive; + default: + return Description::Direction::Unknown; + } +} + +shared_ptr getNewRTPPacketizationConfig(uint32_t ssrc, const char * cname, uint8_t payloadType, uint32_t clockRate, uint16_t sequenceNumber, uint32_t timestamp) { + if (!cname) { + throw std::invalid_argument("Unexpected null pointer for cname"); + } + + return std::make_shared(ssrc, cname, payloadType, clockRate, sequenceNumber, timestamp); +} + +#endif // RTC_ENABLE_MEDIA + #if RTC_ENABLE_WEBSOCKET shared_ptr getWebSocket(int id) { std::lock_guard lock(mutex); @@ -276,6 +338,10 @@ void rtcInitLogger(rtcLogLevel level, rtcLogCallbackFunc cb) { 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({ Configuration c; @@ -295,7 +361,8 @@ int rtcCreatePeerConnection(const rtcConfiguration *config) { int rtcDeletePeerConnection(int pc) { return WRAP({ auto peerConnection = getPeerConnection(pc); - peerConnection->onDataChannel(nullptr); + peerConnection->onDataChannel(nullptr); + peerConnection->onTrack(nullptr); peerConnection->onLocalDescription(nullptr); peerConnection->onLocalCandidate(nullptr); peerConnection->onStateChange(nullptr); @@ -365,6 +432,200 @@ int rtcDeleteDataChannel(int dc) { }); } +#if RTC_ENABLE_MEDIA + +void setSSRC(Description::Media * description, uint32_t ssrc, const char *_name, const char *_msid) { + + optional name = nullopt; + if (_name) { + name = string(_name); + } + + optional msid = nullopt; + if (_msid) { + msid = string(_msid); + } + + description->addSSRC(ssrc, name, msid); +} + +int rtcAddTrackEx(int pc, rtcCodec codec, int payloadType, uint32_t ssrc, const char *_mid, rtcDirection _direction, const char *_name, const char *_msid) { + return WRAP( { + auto peerConnection = getPeerConnection(pc); + + auto direction = rtcDirectionToDirection(_direction); + + string mid = "video"; + switch (codec) { + case RTC_CODEC_H264: + case RTC_CODEC_VP8: + case RTC_CODEC_VP9: + mid = "video"; + break; + case RTC_CODEC_OPUS: + mid = "audio"; + break; + } + + if (_mid) { + mid = string(_mid); + } + + optional optDescription = nullopt; + + switch (codec) { + case RTC_CODEC_H264: + case RTC_CODEC_VP8: + case RTC_CODEC_VP9: { + auto desc = Description::Video(mid, direction); + switch (codec) { + case RTC_CODEC_H264: + desc.addH264Codec(payloadType); + break; + case RTC_CODEC_VP8: + desc.addVP8Codec(payloadType); + break; + case RTC_CODEC_VP9: + desc.addVP8Codec(payloadType); + break; + default: + break; + } + optDescription = desc; + break; + } + case RTC_CODEC_OPUS: { + auto desc = Description::Audio(mid, direction); + switch (codec) { + case RTC_CODEC_OPUS: + desc.addOpusCodec(payloadType); + break; + default: + break; + } + optDescription = desc; + break; + } + default: + break; + } + + if (!optDescription.has_value()) { + throw std::invalid_argument("Unexpected codec"); + } else { + auto description = optDescription.value(); + setSSRC(&description, ssrc, _name, _msid); + + int tr = emplaceTrack(peerConnection->addTrack(std::move(description))); + if (auto ptr = getUserPointer(pc)) { + rtcSetUserPointer(tr, *ptr); + } + return tr; + } + }); +} + +int rtcSetH264PacketizationHandler(int tr, uint32_t ssrc, const char * cname, uint8_t payloadType, uint32_t clockRate, uint16_t sequenceNumber, uint32_t timestamp) { + return WRAP({ + auto track = getTrack(tr); + // create RTP configuration + auto rtpConfig = getNewRTPPacketizationConfig(ssrc, cname, payloadType, clockRate, sequenceNumber, timestamp); + // create packetizer + auto packetizer = shared_ptr(new H264RTPPacketizer(rtpConfig)); + // create H264 and RTCP SP handler + shared_ptr h264Handler(new H264PacketizationHandler(H264PacketizationHandler::Separator::Length, packetizer)); + emplaceRTCPSender(h264Handler, tr); + emplaceRTPConfig(rtpConfig, tr); + // set handler + track->setRtcpHandler(h264Handler); + }); +} + +int rtcSetOpusPacketizationHandler(int tr, uint32_t ssrc, const char * cname, uint8_t payloadType, uint32_t clockRate, uint16_t sequenceNumber, uint32_t timestamp) { + return WRAP({ + auto track = getTrack(tr); + // create RTP configuration + auto rtpConfig = getNewRTPPacketizationConfig(ssrc, cname, payloadType, clockRate, sequenceNumber, timestamp); + // create packetizer + auto packetizer = shared_ptr(new OpusRTPPacketizer(rtpConfig)); + // create Opus and RTCP SP handler + shared_ptr opusHandler(new OpusPacketizationHandler(packetizer)); + emplaceRTCPSender(opusHandler, tr); + emplaceRTPConfig(rtpConfig, tr); + // set handler + track->setRtcpHandler(opusHandler); + }); +} + +int rtcSetRtpConfigurationStartTime(int id, double startTime_s, bool timeIntervalSince1970, uint32_t timestamp) { + return WRAP({ + auto config = getRTPConfig(id); + auto epoch = RTPPacketizationConfig::EpochStart::T1900; + if (timeIntervalSince1970) { + epoch = RTPPacketizationConfig::EpochStart::T1970; + } + config->setStartTime(startTime_s, epoch, timestamp); + }); +} + +int rtcStartRtcpSenderReporterRecording(int id) { + return WRAP({ + auto sender = getRTCPSender(id); + sender->startRecording(); + }); +} + +int rtcTransformSecondsToTimestamp(int id, double seconds, uint32_t * timestamp) { + return WRAP({ + auto config = getRTPConfig(id); + *timestamp = config->secondsToTimestamp(seconds); + }); +} + +int rtcTransformTimestampToSeconds(int id, uint32_t timestamp, double * seconds) { + return WRAP({ + auto config = getRTPConfig(id); + *seconds = config->timestampToSeconds(timestamp); + }); +} + +int rtcGetCurrentTrackTimestamp(int id, uint32_t * timestamp) { + return WRAP({ + auto config = getRTPConfig(id); + *timestamp = config->timestamp; + }); +} + +int rtcGetTrackStartTimestamp(int id, uint32_t * timestamp) { + return WRAP({ + auto config = getRTPConfig(id); + *timestamp = config->startTimestamp; + }); +} + +int rtcSetTrackRTPTimestamp(int id, uint32_t timestamp) { + return WRAP({ + auto config = getRTPConfig(id); + config->timestamp = timestamp; + }); +} + +int rtcGetPreviousTrackSenderReportTimestamp(int id, uint32_t * timestamp) { + return WRAP({ + auto sender = getRTCPSender(id); + *timestamp = sender->previousReportedTimestamp; + }); +} + +int rtcSetNeedsToSendRTCPSR(int id) { + return WRAP({ + auto sender = getRTCPSender(id); + sender->setNeedsToReport(); + }); +} + +#endif // RTC_ENABLE_MEDIA + int rtcAddTrack(int pc, const char *mediaDescriptionSdp) { return WRAP({ if (!mediaDescriptionSdp)