Browse Source

Merge pull request #143 from stazio/webrtc_media

Create API for WebRTC Media Streams
Paul-Louis Ageneau 5 years ago
parent
commit
f6be5d6db0

+ 2 - 0
CMakeLists.txt

@@ -75,6 +75,7 @@ set(LIBDATACHANNEL_HEADERS
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/configuration.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/datachannel.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/description.hpp
+	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtp.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/include.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/init.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/log.hpp
@@ -267,6 +268,7 @@ if(NOT NO_EXAMPLES)
 	set(JSON_BuildTests OFF CACHE INTERNAL "")
 	add_subdirectory(deps/json)
 	add_subdirectory(examples/client)
+	add_subdirectory(examples/media)
 	add_subdirectory(examples/copy-paste)
 	add_subdirectory(examples/copy-paste-capi)
 endif()

+ 2 - 0
examples/README.md

@@ -7,6 +7,8 @@ This directory contains different WebRTC clients and compatible WebSocket + JSON
 - [signaling-server-python](signaling-server-python) contains a similar signaling server in Python
 - [signaling-server-rust](signaling-server-rust) contains a similar signaling server in Rust (see [lerouxrgd/datachannel-rs](https://github.com/lerouxrgd/datachannel-rs) for Rust wrappers)
 
+- [media](media) is a copy/paste demo to send the webcam from your browser into gstreamer.
+
 Additionally, it contains two debugging tools for libdatachannel with copy-pasting as signaling:
 - [copy-paste](copy-paste) using the C++ API
 - [copy-paste-capi](copy-paste-capi) using the C API

+ 14 - 0
examples/media/CMakeLists.txt

@@ -0,0 +1,14 @@
+cmake_minimum_required(VERSION 3.7)
+
+add_executable(datachannel-media main.cpp)
+set_target_properties(datachannel-media PROPERTIES
+        CXX_STANDARD 17
+        OUTPUT_NAME media)
+
+if(WIN32)
+    target_link_libraries(datachannel-media datachannel-static) # DLL exports only the C API
+else()
+    target_link_libraries(datachannel-media datachannel)
+endif()
+
+target_link_libraries(datachannel-media datachannel nlohmann_json)

+ 19 - 0
examples/media/README.md

@@ -0,0 +1,19 @@
+# Example Webcam from Browser to Port 5000
+This is an example copy/paste demo to send your webcam from your browser and out port 5000 through the demo application.
+
+## How to use
+Open main.html in your browser (you must open it either as HTTPS or as a domain of http://localhost).
+
+Start the application and copy it's offer into the text box of the web page.
+
+Copy the answer of the webpage back into the application.
+
+You will now see RTP traffic on `localhost:5000` of the computer that the application is running on.
+
+Use the following gstreamer demo pipeline to display the traffic 
+(you might need to wave your hand in front of your camera to force an I-frame). 
+
+`gst-launch-1.0 udpsrc address=127.0.0.1 port=5000 caps="application/x-rtp" ! queue ! rtph264depay ! video/x-h264,stream-format=byte-stream ! queue ! avdec_h264 ! queue ! autovideosink`
+
+## Troubleshooting
+Use chrome.

+ 70 - 0
examples/media/main.cpp

@@ -0,0 +1,70 @@
+#include <iostream>
+#include <memory>
+#include <rtc/log.hpp>
+#include <rtc/rtc.hpp>
+#include <rtc/rtp.hpp>
+
+#include <nlohmann/json.hpp>
+#include <utility>
+#include <arpa/inet.h>
+
+using nlohmann::json;
+
+int main() {
+    rtc::InitLogger(rtc::LogLevel::Debug);
+    auto pc = std::make_shared<rtc::PeerConnection>();
+
+    pc->onStateChange([](rtc::PeerConnection::State state) { std::cout << "State: " << state << std::endl; });
+
+    pc->onGatheringStateChange( [pc](rtc::PeerConnection::GatheringState state) {
+        std::cout << "Gathering State: " << state << std::endl;
+        if (state == rtc::PeerConnection::GatheringState::Complete) {
+            auto description = pc->localDescription();
+            json message = {
+                    {"type", description->typeString()}, {"sdp", std::string(description.value())}
+            };
+            std::cout << message << std::endl;
+        }
+    });
+
+    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
+    sockaddr_in addr;
+    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
+    addr.sin_port = htons(5000);
+    addr.sin_family = AF_INET;
+
+    RTCPSession session;
+    session.setOutgoingCallback([&pc](const rtc::message_ptr& ptr) {
+        // There is a chance we could send an RTCP packet before we connect (i.e. REMB)
+        if (pc->state() == rtc::PeerConnection::State::Connected) {
+            pc->sendMedia(ptr);
+        }
+    });
+//    session.requestBitrate(4000000); // Request 3Mbps (Browsers do not encode more than 2.5MBps from a webcam)
+    pc->onMedia([&session, &sock_fd, &addr](const rtc::message_ptr& ptr) {
+        auto resp = session.onData(ptr);
+        if (resp.has_value()) {
+            // This is an RTP packet
+            sendto(sock_fd, resp.value()->data(), resp.value()->size(), 0, (const struct sockaddr*) &addr, sizeof(addr));
+        }
+    });
+
+    rtc::Description offer;
+    rtc::Description::Media &m = offer.addVideoMedia(rtc::Description::RecvOnly);
+    m.addH264Codec(96);
+    m.setBitrate(3000); // Request 3Mbps (Browsers do not encode more than 2.5MBps from a webcam)
+//    m.setBitrate(5000000);
+    pc->setLocalDescription(offer);
+    auto dc = pc->createDataChannel("test");
+
+    std::cout << "Expect RTP video traffic on localhost:5000" << std::endl;
+    std::cout << "Please copy/paste the answer provided by the browser: " << std::endl;
+    std::string sdp;
+    std::getline (std::cin,sdp);
+    std::cout << "Got answer" << sdp << std::endl;
+    json j = json::parse(sdp);
+    rtc::Description answer(j["sdp"].get<std::string>(), j["type"].get<std::string>());
+    pc->setRemoteDescription(answer, answer);
+    std::cout << "Press any key to exit." << std::endl;
+    std::cin >> sdp;
+}

+ 56 - 0
examples/media/main.html

@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Title</title>
+</head>
+<body>
+
+<p>Please enter the offer provided to you by the application: </p>
+<textarea cols="50" rows="50"></textarea>
+<button>Submit</button>
+
+<script>
+    document.querySelector('button').addEventListener('click',  async () => {
+        let offer = JSON.parse(document.querySelector('textarea').value);
+        rtc = new RTCPeerConnection({
+            // Requirement of libdatachannel
+            bundlePolicy: "max-bundle",
+        });
+
+        rtc.onicegatheringstatechange = (state) => {
+            if (rtc.iceGatheringState === 'complete') {
+                // We only want to provide an answer once all of our candidates have been added to the SDP.
+                let answer = rtc.localDescription;
+                document.querySelector('textarea').value = JSON.stringify({"type": answer.type, sdp: answer.sdp});
+                document.querySelector('p').value = 'Please paste the answer in the application.';
+                alert('Please paste the answer in the application.');
+            }
+        }
+        await rtc.setRemoteDescription(offer);
+
+        let media = await navigator.mediaDevices.getUserMedia({
+            video: {
+                width: 1280,
+                height: 720
+            }
+        });
+        media.getTracks().forEach(track => rtc.addTrack(track, media));
+        let answer= await  rtc.createAnswer();
+        await rtc.setLocalDescription(answer);
+
+        // For some reason, (at least) chrome requires for you to manually add the candidates.
+        offer.sdp.split("\n").forEach(line => {
+            if (line.startsWith("a=candidate")) {
+                let cand = line.substring(2);
+                rtc.addIceCandidate({
+                    sdpMid: "video",
+                    candidate: cand.trim()
+                });
+            }
+        });
+    })
+</script>
+
+</body>
+</html>

+ 68 - 17
include/rtc/description.hpp

@@ -29,14 +29,23 @@
 
 namespace rtc {
 
+
 class Description {
 public:
-	enum class Type { Unspec = 0, Offer = 1, Answer = 2 };
-	enum class Role { ActPass = 0, Passive = 1, Active = 2 };
 
-	Description(const string &sdp, const string &typeString = "");
+    enum class Type { Unspec = 0, Offer = 1, Answer = 2 };
+	enum class Role { ActPass = 0, Passive = 1, Active = 2 };
+    enum Direction {
+        SendOnly,
+        RecvOnly,
+        SendRecv,
+        Unknown
+    };
+
+    Description(const string &sdp, const string &typeString = "");
 	Description(const string &sdp, Type type);
 	Description(const string &sdp, Type type, Role role);
+    Description(): Description(""){};
 
 	Type type() const;
 	string typeString() const;
@@ -67,20 +76,60 @@ public:
 	string generateDataSdp(const string &eol) const;
 
 	// Data
-	struct Data {
-		string mid;
-		std::optional<uint16_t> sctpPort;
-		std::optional<size_t> maxMessageSize;
-	};
-
-	// Media (non-data)
-	struct Media {
-		Media(const string &mline);
-		string type;
-		string description;
-		string mid;
-		std::vector<string> attributes;
-	};
+    struct Data {
+        string mid;
+        std::optional<uint16_t> sctpPort;
+        std::optional<size_t> maxMessageSize;
+    };
+
+    // Media (non-data)
+    struct Media {
+        Media(const string &lines);
+        string type;
+        string description;
+        string mid;
+        std::vector<string> attributes;
+        std::vector<string> attributesl;
+        int bAS = -1;
+
+        struct RTPMap {
+            RTPMap(const string &mLine);
+
+            int pt;
+            string format;
+            int clockRate;
+            string encParams;
+
+            std::vector<string> rtcpFbs;
+            std::vector<string> fmtps;
+
+            void removeFB(const string& string);
+            void addFB(const string& string);
+        };
+
+        std::unordered_map<int, RTPMap> rtpMap;
+
+        Media::RTPMap& getFormat(int fmt);
+        Media::RTPMap& getFormat(const string& fmt);
+        void removeFormat(const string &fmt);
+
+        Direction getDirection();
+        void setDirection(Direction dir);
+
+        void addVideoCodec(int payloadType, const string& codec);
+        void addH264Codec(int payloadType);
+        void addVP8Codec(int payloadType);
+        void addVP9Codec(int payloadType);
+
+        void setBitrate(int bitrate);
+        int getBitrate() const;
+    };
+
+    std::_Rb_tree_iterator<std::pair<const int, Media>> getMedia(int mLine);
+
+    Media & addAudioMedia();
+
+    Media &addVideoMedia(Direction direction=Direction::RecvOnly);
 
 private:
 	Type mType;
@@ -88,7 +137,9 @@ private:
 	string mSessionId;
 	string mIceUfrag, mIcePwd;
 	std::optional<string> mFingerprint;
+
 	Data mData;
+  
 	std::map<int, Media> mMedia; // by m-line index
 
 	// Candidates

+ 1 - 0
include/rtc/peerconnection.hpp

@@ -103,6 +103,7 @@ public:
 
 	// Media support requires compilation with SRTP
 	bool hasMedia() const;
+
 	std::shared_ptr<Track> createTrack(Description::Media description);
 	void onTrack(std::function<void(std::shared_ptr<Track> track)> callback);
 

+ 521 - 0
include/rtc/rtp.hpp

@@ -0,0 +1,521 @@
+#ifndef RTC_RTPL_H
+#define RTC_RTPL_H
+
+#include <cmath>
+
+#include <utility>
+#include <netinet/in.h>
+
+typedef uint32_t SSRC;
+
+struct RTCP_ReportBlock {
+private:
+    SSRC ssrc;
+
+    /** fraction lost is 8 bits; packets lost is 24 bits */
+    uint32_t fractionLostAndPacketsLost;
+
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+    uint32_t seqNoCycles:16;
+    uint32_t highestSeqNo:16;
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+    uint32_t highestSeqNo:16;
+    uint32_t seqNoCycles:16;
+#endif
+
+    uint32_t arrivalJitter;
+    uint32_t lastReport;
+    uint32_t delaySinceLastReport;
+
+public:
+    void print() {
+        std::cout <<
+            " ssrc:" << ntohl(ssrc) <<
+            // TODO Implement these reports
+//            " fractionLost: " << fractionLost <<
+//            " packetsLost: " << packetsLost <<
+            " highestSeqNo:" <<getHighestSeqNo() <<
+            " seqNoCycles:" << getSeqCycleCount() <<
+            " jitter:" << getJitter() <<
+            " lastSR:" << getNTPOfSR() <<
+            " lastSRDelay:" << getDelaySinceSR();
+    }
+
+    void preparePacket(SSRC ssrc, unsigned int packetsLost, unsigned int totalPackets, uint16_t highestSeqNo, uint16_t seqNoCycles, uint32_t jitter, uint64_t lastSR_NTP, uint64_t lastSR_DELAY) {
+        setSeqNo(highestSeqNo, seqNoCycles);
+        setJitter(jitter);
+        setSSRC(ssrc);
+
+        // Middle 32 bits of NTP Timestamp
+//        this->lastReport = lastSR_NTP >> 16u;
+        setNTPOfSR(lastSR_NTP);
+        setDelaySinceSR(lastSR_DELAY);
+
+        // The delay, expressed in units of 1/65536 seconds
+//        this->delaySinceLastReport = lastSR_DELAY;
+    }
+
+    void inline setSSRC(SSRC ssrc) {
+        this->ssrc = htonl(ssrc);
+    }
+    SSRC inline getSSRC() const {return ntohl(ssrc);}
+
+    void inline setPacketsLost(unsigned int packetsLost, unsigned int totalPackets) {
+        // TODO Implement loss percentages.
+        this->fractionLostAndPacketsLost = 0;
+    }
+    unsigned int inline getLossPercentage() const {
+        // TODO Implement loss percentages.
+        return 0;
+    }
+    unsigned int inline getPacketLostCount() const {
+        // TODO Implement total packets lost.
+        return 0;
+    }
+
+    void inline setSeqNo(uint16_t highestSeqNo, uint16_t seqNoCycles) {
+        this->highestSeqNo = htons(highestSeqNo);
+        this->seqNoCycles = htons(seqNoCycles);
+    }
+
+    uint16_t inline getHighestSeqNo() const {
+        return ntohs(this->highestSeqNo);
+    }
+    uint16_t inline getSeqCycleCount() const {
+        return ntohs(this->seqNoCycles);
+    }
+
+    uint32_t inline getJitter() const {
+        return ntohl(arrivalJitter);
+    }
+    void inline setJitter(uint32_t jitter) {
+        this->arrivalJitter = htonl(jitter);
+    }
+
+    void inline setNTPOfSR(uint32_t ntp) {
+        lastReport = htonl(ntp >> 16u);
+    }
+    inline uint32_t getNTPOfSR() const  {
+        return ntohl(lastReport) << 16u;
+    }
+    inline void setDelaySinceSR(uint32_t sr) {
+        // The delay, expressed in units of 1/65536 seconds
+        delaySinceLastReport = htonl(sr);
+    }
+    inline uint32_t getDelaySinceSR() const {
+        return ntohl(delaySinceLastReport);
+    }
+};
+
+
+struct RTCP_HEADER {
+private:
+#if __BYTE_ORDER == __BIG_ENDIAN
+    uint16_t version:2;
+	uint16_t padding:1;
+	uint16_t rc:5;
+	uint16_t payloadType:8;
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+    uint16_t reportCount:5;
+    uint16_t padding:1;
+    uint16_t version:2;
+    uint16_t payloadType:8;
+#endif
+    uint16_t length;
+
+public:
+    void prepareHeader(uint8_t payloadType, unsigned int reportCount, uint16_t length) {
+        version = 2;
+        padding = false;
+        this->payloadType = payloadType;
+        this->reportCount = reportCount;
+        setLength(length);
+    }
+
+    inline uint8_t getPayloadType() const {
+        return payloadType;
+    }
+    inline void setPayloadType(uint8_t payloadType) {
+        this->payloadType = payloadType;
+    }
+
+    inline uint8_t getReportCount() const {
+        return reportCount;
+    }
+
+    inline void setReportCount(uint8_t reportCount) {
+        this->reportCount = reportCount;
+    }
+
+    inline uint16_t getLength() const {
+        return ntohs(length);
+    }
+
+    inline void setLength(uint16_t length) {
+        this->length = htons(length);
+    }
+
+    void print() {
+        std::cout <<
+        "version:" << (uint16_t) version <<
+                   " padding:" << (padding ? "T" : "F") <<
+                   " reportCount: " <<(uint16_t) getReportCount() <<
+                   " payloadType:" << (uint16_t)getPayloadType() <<
+                   " length: " << getLength();
+    }
+};
+
+struct RTCP_SR {
+private:
+    RTCP_HEADER header;
+
+    SSRC senderSSRC;
+
+    uint64_t ntpTimestamp;
+    uint32_t rtpTimestamp;
+
+    uint32_t packetCount;
+    uint32_t octetCount;
+
+    RTCP_ReportBlock reportBlocks;
+public:
+
+    void print() {
+        std::cout << "SR ";
+        header.print();
+        std::cout <<
+              " SSRC:" << ntohl(senderSSRC) <<
+              " NTP TS: " << ntpTimestamp << // TODO This needs to be convereted from network-endian
+              " RTP TS: " << ntohl(rtpTimestamp) <<
+              " packetCount: " << ntohl(packetCount) <<
+              " octetCount: " << ntohl(octetCount) << "\n";
+//        << std::endl;
+            for (int i =0;i < header.getReportCount(); i++) {
+                getReportBlock(i)->print();
+                std::cout << "\n";
+            }
+    }
+
+
+    inline void preparePacket(SSRC senderSSRC, uint8_t reportCount) {
+        unsigned int length = ( (offsetof(RTCP_SR, reportBlocks) + reportCount*sizeof(RTCP_ReportBlock))/4 )-1;
+        header.prepareHeader(200, reportCount, length);
+        this->senderSSRC = senderSSRC;
+    }
+
+    RTCP_ReportBlock* getReportBlock(int num) {
+        return &reportBlocks+num;
+    }
+
+    [[nodiscard]] unsigned int getSize() const {
+        // "length" in packet is one less than the number of 32 bit words in the packet.
+        return sizeof(uint32_t) * (1+header.getLength());
+    }
+
+    inline uint32_t getRTPTS() const{
+        return ntohl(rtpTimestamp);
+    }
+    inline uint32_t getNTPTS() const{
+        return ntohl(ntpTimestamp);
+    }
+
+    inline void setRTPTS(uint32_t ts) {
+        this->rtpTimestamp = htons(ts);
+    }
+    inline void setNTPTS(uint32_t ts) {
+        this->ntpTimestamp = htons(ts);
+    }
+};
+
+struct RTCP_RR {
+private:
+    RTCP_HEADER header;
+    SSRC senderSSRC;
+    RTCP_ReportBlock reportBlocks;
+
+public:
+    void print() {
+        std::cout << "RR ";
+        header.print();
+        std::cout <<
+//                  "version:" << (uint16_t) version <<
+//                  " padding:" << (padding ? "T" : "F") <<
+//                  " reportCount: " << (uint16_t) reportCount <<
+//                  " payloadType:" << (uint16_t) payloadType <<
+//                  " totalLength:" << ntohs(length) <<
+                  " SSRC:" << ntohl(senderSSRC) << "\n";
+//                  << std::endl;
+        for (int i =0;i < header.getReportCount(); i++) {
+            getReportBlock(i)->print();
+            std::cout << "\n";
+        }
+    }
+    RTCP_ReportBlock* getReportBlock(int num) {
+        return &reportBlocks+num;
+    }
+
+    inline RTCP_HEADER& getHeader() {
+        return header;
+    }
+
+    inline SSRC getSenderSSRC() const {
+        return ntohl(senderSSRC);
+    }
+
+    inline void setSenderSSRC(SSRC ssrc) {
+        this->senderSSRC = ssrc;
+    }
+
+    [[nodiscard]] inline unsigned int getSize() const {
+        // "length" in packet is one less than the number of 32 bit words in the packet.
+        return sizeof(uint32_t) * (1+header.getLength());
+    }
+
+    inline void preparePacket(SSRC senderSSRC, uint8_t reportCount) {
+//        version = 2;
+//        padding = false;
+//        this->reportCount = reportCount;
+//        payloadType = 201;
+//        // "length" in packet is one less than the number of 32 bit words in the packet.
+        unsigned int length = ( (offsetof(RTCP_RR, reportBlocks) + reportCount*sizeof(RTCP_ReportBlock))/4 )-1;
+        header.prepareHeader(201, reportCount, length);
+        this->senderSSRC = htonl(senderSSRC);
+    }
+
+    static unsigned inline int sizeWithReportBlocks(int reportCount) {
+        return offsetof(RTCP_RR, reportBlocks) + reportCount*sizeof(RTCP_ReportBlock);
+    }
+};
+
+struct RTP
+{
+#if __BYTE_ORDER == __BIG_ENDIAN
+    uint16_t version:2;
+	uint16_t padding:1;
+	uint16_t extension:1;
+	uint16_t csrcCount:4;
+	uint16_t markerBit:1;
+	uint16_t payloadType:7;
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+    uint16_t csrCcount:4;
+    uint16_t extension:1;
+    uint16_t padding:1;
+    uint16_t version:2;
+    uint16_t payloadType:7;
+    uint16_t markerBit:1;
+#endif
+    uint16_t seqNumber;
+    uint32_t timestamp;
+    SSRC ssrc;
+    SSRC csrc[16];
+
+    inline uint32_t getSeqNo() const {
+        return ntohs(seqNumber);
+    }
+
+    inline uint32_t getTS() const{
+        return ntohl(timestamp);
+    }
+};
+
+ struct RTCP_REMB
+{
+     RTCP_HEADER header;
+
+    SSRC senderSSRC;
+    SSRC mediaSourceSSRC;
+
+    /*! \brief Unique identifier ('R' 'E' 'M' 'B') */
+    char id[4];
+
+    /*! \brief Num SSRC, Br Exp, Br Mantissa (bit mask) */
+    uint32_t bitrate;
+
+    SSRC ssrc[1];
+
+    [[nodiscard]] unsigned int getSize() const {
+        // "length" in packet is one less than the number of 32 bit words in the packet.
+        return sizeof(uint32_t) * (1+header.getLength());
+    }
+
+    void preparePacket(SSRC senderSSRC, unsigned int numSSRC, unsigned int bitrate) {
+//        version = 2;
+//        format = 15;
+//        padding = false;
+//        payloadType = 206;
+
+        // Report Count becomes the format here.
+        header.prepareHeader(206, 15, 0);
+
+        // Always zero.
+        mediaSourceSSRC = 0;
+
+        this->senderSSRC = htonl(senderSSRC);
+        id[0] = 'R';
+        id[1] = 'E';
+        id[2] = 'M';
+        id[3] = 'B';
+
+        setBitrate(numSSRC, bitrate);
+    }
+
+    void setBitrate(unsigned int numSSRC, unsigned int bitrate) {
+        unsigned int exp = 0;
+        while (bitrate > pow(2,18)-1) {
+            exp++;
+            bitrate /= 2;
+        }
+
+        // "length" in packet is one less than the number of 32 bit words in the packet.
+        header.setLength((offsetof(RTCP_REMB, ssrc)/4)-1+numSSRC);
+
+        this->bitrate = htonl(
+                (numSSRC << (32u-8u)) | (exp << (32u-8u-6u)) | bitrate
+        );
+    }
+
+    //TODO Make this work
+//    uint64_t getBitrate() const{
+//        uint32_t ntohed = ntohl(this->bitrate);
+//        uint64_t bitrate = ntohed & (unsigned int)(pow(2, 18)-1);
+//        unsigned int exp = ntohed & ((unsigned int)( (pow(2, 6)-1)) << (32u-8u-6u));
+//        return bitrate * pow(2,exp);
+//    }
+//
+//    uint8_t getNumSSRCS() const {
+//        return ntohl(this->bitrate) & (((unsigned int) pow(2,8)-1) << (32u-8u));
+//    }
+
+    void print() {
+        std::cout << "REMB ";
+        header.print();
+        std::cout << " SSRC:" << ntohl(senderSSRC);
+    }
+
+    void setSSRC(uint8_t iterator, SSRC ssrc) {
+        this->ssrc[iterator] = htonl(ssrc);
+    }
+
+    static unsigned int sizeWithSSRCs(int numSSRC) {
+        return (offsetof(RTCP_REMB, ssrc))+sizeof(SSRC)*numSSRC;
+    }
+};
+
+class RTCPSession {
+private:
+    std::function<void(RTP)> onPacketCB;
+    unsigned int requestedBitrate = 0;
+//    synchronized_callback<const rtc::message_ptr&> txCB;
+    std::function<void(rtc::message_ptr)> txCB;
+    SSRC ssrc;
+    uint32_t greatestSeqNo = 0, greatestTS;
+    uint64_t syncRTPTS, syncNTPTS;
+
+    unsigned int rotationCount = 0;
+
+
+public:
+    std::vector<rtc::message_ptr> sendQueue;
+//    RTCPSession(  callback): txCB(std::move(callback)) {}
+//    RTCPSession(const std::function<void(rtc::message_ptr)>& callback, SSRC ssrc) : ssrc(ssrc) {}
+
+    void setOutgoingCallback(const std::function<void(rtc::message_ptr)>& callback) {
+        txCB = callback;
+    }
+
+    std::optional<rtc::message_ptr> onData(rtc::message_ptr ptr) {
+        if (ptr->type == rtc::Message::Type::Binary) {
+            RTP* rtp = (RTP*) ptr->data();
+
+            // https://tools.ietf.org/html/rfc3550#appendix-A.1
+            if (rtp->version != 2) {
+                PLOG_WARNING << "RTP packet is not version 2";
+
+                return std::nullopt;
+            }
+            if (rtp->payloadType == 201 || rtp->payloadType == 200) {
+                PLOG_WARNING << "RTP packet has a payload type indicating RR/SR";
+
+                return std::nullopt;
+            }
+
+            // TODO Implement the padding bit
+            if (rtp->padding) {
+                PLOG_WARNING << "Padding processing not implemented";
+            }
+
+            ssrc = ntohl(rtp->ssrc);
+
+            uint32_t seqNo = rtp->getSeqNo();
+            uint32_t rtpTS = rtp->getTS();
+
+            if (greatestSeqNo < seqNo)
+                greatestSeqNo = seqNo;
+
+            return ptr;
+        }
+//        return std::nullopt;
+        assert(ptr->type == rtc::Message::Type::Control);
+        auto rr = (RTCP_RR*) ptr->data();
+        if (rr->getHeader().getPayloadType() == 201) {
+            // RR
+            ssrc = rr->getSenderSSRC();
+            rr->print();
+            std::cout << std::endl;
+        }else if (rr->getHeader().getPayloadType() == 200){
+            // SR
+            ssrc = rr->getSenderSSRC();
+            auto sr = (RTCP_SR*) ptr->data();
+            syncRTPTS = sr->getRTPTS();
+            syncNTPTS = sr->getNTPTS();
+            sr->print();
+            std::cout << std::endl;
+
+            // TODO For the time being, we will send RR's/REMB's when we get an SR
+            pushRR(0);
+            if (requestedBitrate > 0)
+                pushREMB(requestedBitrate);
+        }
+        return std::nullopt;
+    }
+
+    void requestBitrate(unsigned int newBitrate) {
+        this->requestedBitrate = newBitrate;
+
+        PLOG_DEBUG << "[GOOG-REMB] Requesting bitrate: " << newBitrate << std::endl;
+        pushREMB(newBitrate);
+    }
+
+private:
+    void pushREMB(unsigned int bitrate) {
+        rtc::message_ptr msg = rtc::make_message(RTCP_REMB::sizeWithSSRCs(1), rtc::Message::Type::Control);
+        auto remb = (RTCP_REMB*) msg->data();
+        remb->preparePacket(ssrc, 1, bitrate);
+        remb->setSSRC(0, ssrc);
+        remb->print();
+        std::cout << std::endl;
+
+//        std::cout << "begin tx" << std::endl;
+        txCB(msg);
+//        sendQueue.emplace_back(msg);
+//        std::cout << "end tx" << std::endl;
+    }
+
+    void pushRR(unsigned int lastSR_delay) {
+//        std::cout << "size " << RTCP_RR::sizeWithReportBlocks(1) << std::endl;
+        auto msg = rtc::make_message(RTCP_RR::sizeWithReportBlocks(1), rtc::Message::Type::Control);
+        auto rr = (RTCP_RR*) msg->data();
+        rr->preparePacket(ssrc, 1);
+        rr->getReportBlock(0)->preparePacket(ssrc, 0, 0, greatestSeqNo, 0, 0, syncNTPTS, lastSR_delay);
+        rr->print();
+        std::cout << std::endl;
+
+//        std::cout << "begin tx" << std::endl;
+
+//        sendQueue.emplace_back(msg);
+         txCB(msg);
+//        std::cout << "end tx" << std::endl;
+    }
+};
+#endif //RTC_RTPL_H

+ 223 - 7
src/description.cpp

@@ -132,11 +132,38 @@ Description::Description(const string &sdp, Type type, Role role)
 			} else if (key == "candidate") {
 				addCandidate(Candidate(attr, currentMedia ? currentMedia->mid : mData.mid));
 			} else if (key == "end-of-candidates") {
-				mEnded = true;
+                mEnded = true;
 			} else if (currentMedia) {
-				currentMedia->attributes.emplace_back(line.substr(2));
+			    if (key == "rtpmap") {
+                    Description::Media::RTPMap map(value);
+                    currentMedia->rtpMap.insert(std::pair<int, Description::Media::RTPMap>(map.pt, map));
+                }else if (key == "rtcp-fb") {
+                    size_t p = value.find(' ');
+                    int pt = std::stoi(value.substr(0, p));
+                    auto it = currentMedia->rtpMap.find(pt);
+                    if (it == currentMedia->rtpMap.end()) {
+                        PLOG_WARNING << "rtcp-fb applied before it's rtpmap. Ignoring";
+                    } else
+                        it->second.rtcpFbs.emplace_back(value.substr(p + 1));
+                }else if (key == "fmtp") {
+                    size_t p = value.find(' ');
+                    int pt = std::stoi(value.substr(0, p));
+                    auto it = currentMedia->rtpMap.find(pt);
+                    if (it == currentMedia->rtpMap.end()) {
+                        PLOG_WARNING << "fmtp applied before it's rtpmap. Ignoring";
+                    } else
+                        it->second.fmtps.emplace_back(value.substr(p + 1));
+
+                } else if (key == "b") {
+
+                }else
+				    currentMedia->attributes.emplace_back(line.substr(2));
+			    std::cout << key << std::endl;
 			}
+		}else if (match_prefix(line, "b=AS")) {
+            currentMedia->bAS = std::stoi(line.substr(line.find(':')+1));
 		}
+
 	} while (!finished);
 }
 
@@ -252,13 +279,34 @@ string Description::generateSdp(const string &eol) const {
 		if (auto it = mMedia.find(i); it != mMedia.end()) {
 			// Non-data media
 			const auto &media = it->second;
-			sdp << "m=" << media.type << ' ' << 0 << ' ' << media.description << eol;
+			sdp << "m=" << media.type << ' ' << 0 << ' ' << media.description;
+
+            for (const auto& [key, _] : media.rtpMap)
+                sdp << " " << key;
+
+            sdp << eol;
 			sdp << "c=IN IP4 0.0.0.0" << eol;
+            if (media.bAS > -1)
+                sdp << "b=AS:" << media.bAS << eol;
 			sdp << "a=bundle-only" << eol;
 			sdp << "a=mid:" << media.mid << eol;
 			for (const auto &attr : media.attributes)
 				sdp << "a=" << attr << eol;
-
+			for (const auto& [_, map] : media.rtpMap) {
+			    // Create the a=rtpmap
+			    sdp << "a=rtpmap:" << map.pt << " " << map.format << "/" << map.clockRate;
+			    if (!map.encParams.empty())
+			        sdp << "/" << map.encParams;
+			    sdp << eol;
+
+
+                for (const auto &val : map.rtcpFbs)
+                    sdp << "a=rtcp-fb:" << map.pt << " " << val << eol;
+                for (const auto &val : map.fmtps)
+                    sdp << "a=fmtp:" << map.pt << " " << val << eol;
+			}
+            for (const auto &attr : media.attributesl)
+                sdp << "a=" << attr << eol;
 		} else {
 			// Data
 			const string description = "UDP/DTLS/SCTP webrtc-datachannel";
@@ -328,8 +376,152 @@ Description::Media::Media(const string &mline) {
 	size_t p = mline.find(' ');
 	this->type = mline.substr(0, p);
 	if (p != string::npos)
-		if (size_t q = mline.find(' ', p + 1); q != string::npos)
-			this->description = mline.substr(q + 1);
+		if (size_t q = mline.find(' ', p + 1); q != string::npos) {
+            this->description = mline.substr(q + 1, mline.find(' ', q+1)-q-2);
+        }
+}
+
+Description::Media::RTPMap& Description::Media::getFormat(int fmt) {
+    auto it = this->rtpMap.find(fmt);
+    if (it == this->rtpMap.end())
+        throw std::invalid_argument("mLineIndex is out of bounds");
+    return it->second;
+}
+
+Description::Media::RTPMap& Description::Media::getFormat(const string& fmt) {
+    for (auto &[key, val] : this->rtpMap) {
+        if (val.format == fmt)
+            return val;
+    }
+    throw std::invalid_argument("format was not found");
+}
+
+void Description::Media::removeFormat(const string &fmt) {
+    auto it = this->rtpMap.begin();
+    std::vector<int> remed;
+
+    // Remove the actual formats
+    while (it != this->rtpMap.end()) {
+        if (it->second.format == fmt) {
+            remed.emplace_back(it->first);
+            it = this->rtpMap.erase(it);
+        } else
+            it++;
+    }
+
+    // Remove any other rtpmaps that depend on the formats we just removed
+    it = this->rtpMap.begin();
+    while (it != this->rtpMap.end()) {
+        auto it2 = it->second.fmtps.begin();
+        bool rem = false;
+        while (it2 != it->second.fmtps.end()) {
+            if (it2->find("apt=") == 0) {
+                for (auto remid : remed) {
+                    if (it2->find(std::to_string(remid)) != string::npos) {
+                        std::cout << *it2 << " " << remid << std::endl;
+                        it = this->rtpMap.erase(it);
+                        rem = true;
+                        break;
+                    }
+                }
+                break;
+            }
+            it2++;
+        }
+        if (!rem)
+
+            it++;
+    }
+}
+
+void Description::Media::addVideoCodec(int payloadType, const string &codec) {
+    RTPMap map(std::to_string(payloadType) + " " + codec + "/90000");
+    map.addFB("nack");
+    map.addFB("goog-remb");
+    this->rtpMap.insert(std::pair<int, RTPMap>(map.pt, map));
+}
+
+void Description::Media::addH264Codec(int pt) {
+    addVideoCodec(pt, "H264");
+}
+
+void Description::Media::addVP8Codec(int payloadType) {
+    addVideoCodec(payloadType, "VP8");
+}
+
+void Description::Media::addVP9Codec(int payloadType) {
+    addVideoCodec(payloadType, "VP9");
+}
+
+Description::Direction Description::Media::getDirection() {
+    for (auto attr : attributes) {
+        if (attr == "sendrecv")
+            return Direction::SendRecv;
+        if (attr == "recvonly")
+            return Direction::RecvOnly;
+        if (attr == "sendonly")
+            return Direction::SendOnly;
+    }
+    return Direction::Unknown;
+}
+
+void Description::Media::setBitrate(int bitrate) {
+    this->bAS = bitrate;
+}
+int Description::Media::getBitrate() const {
+    return this->bAS;
+}
+
+void Description::Media::setDirection(Description::Direction dir) {
+    auto it = attributes.begin();
+    while (it != attributes.end()) {
+        if (*it == "sendrecv" || *it == "sendonly" || *it == "recvonly")
+            it = attributes.erase(it);
+        else
+            it++;
+    }
+    if (dir == Direction::SendRecv)
+        attributes.emplace(attributes.begin(), "sendrecv");
+    else if (dir == Direction::RecvOnly)
+        attributes.emplace(attributes.begin(), "recvonly");
+    if (dir == Direction::SendOnly)
+        attributes.emplace(attributes.begin(), "sendonly");
+}
+
+Description::Media::RTPMap::RTPMap(const string &mline) {
+    size_t p = mline.find(' ');
+
+    this->pt = std::stoi(mline.substr(0, p));
+
+    auto line = mline.substr(p+1);
+    size_t spl = line.find('/');
+    this->format = line.substr(0, spl);
+
+    line = line.substr(spl+1);
+    spl = line.find('/');
+    if (spl == string::npos) {
+        spl = line.find(' ');
+    }
+    if (spl == string::npos)
+        this->clockRate = std::stoi(line);
+    else {
+        this->clockRate = std::stoi(line.substr(0, spl));
+        this->encParams = line.substr(spl);
+    }
+}
+
+void Description::Media::RTPMap::removeFB(const string& string) {
+        auto it = rtcpFbs.begin();
+        while (it != rtcpFbs.end()) {
+            if (it->find(string) != std::string::npos) {
+                it = rtcpFbs.erase(it);
+            } else
+                it++;
+        }
+    }
+
+void Description::Media::RTPMap::addFB(const string& string) {
+    rtcpFbs.emplace_back(string);
 }
 
 Description::Type Description::stringToType(const string &typeString) {
@@ -363,9 +555,33 @@ string Description::roleToString(Role role) {
 	}
 }
 
-} // namespace rtc
+std::_Rb_tree_iterator<std::pair<const int, Description::Media>> Description::getMedia(int mLine) {
+    return this->mMedia.find(mLine);
+}
+
+rtc::Description::Media& Description::addAudioMedia() {
+    rtc::Description::Media media("audio 9 UDP/TLS/RTP/SAVPF");
+    media.mid = "audio";
+
+    media.attributes.emplace_back("rtcp-mux");
+
+    this->mMedia.insert(std::pair<int, Media>(this->mMedia.size(), media));
+    return this->mMedia.at(mMedia.size()-1);
+}
+
+Description::Media& Description::addVideoMedia(Description::Direction direction) {
+    rtc::Description::Media media("video 9 UDP/TLS/RTP/SAVPF");
+    media.mid = "video";
+    media.attributes.emplace_back("rtcp-mux");
+
+    media.setDirection(direction);
+
+    this->mMedia.insert(std::pair<int, Media>(this->mMedia.size(), media));
+    return this->mMedia.at(mMedia.size()-1);
+}
 
 std::ostream &operator<<(std::ostream &out, const rtc::Description &description) {
 	return out << std::string(description);
 }
 
+}

+ 5 - 3
src/dtlssrtptransport.cpp

@@ -84,9 +84,11 @@ bool DtlsSrtpTransport::sendMedia(message_ptr message) {
 
 	int size = message->size();
 	PLOG_VERBOSE << "Send size=" << size;
+//    return outgoing(message);
 
 	// The RTP header has a minimum size of 12 bytes
-	if (size < 12)
+	// An RTCP packet can have a minimum size of 8 bytes
+	if (size < 8)
 		throw std::runtime_error("RTP/RTCP packet too short");
 
 	// srtp_protect() and srtp_protect_rtcp() assume that they can write SRTP_MAX_TRAILER_LEN (for
@@ -124,8 +126,8 @@ bool DtlsSrtpTransport::sendMedia(message_ptr message) {
 	}
 
 	message->resize(size);
-	outgoing(message);
-	return true;
+	return outgoing(message);
+//	return DtlsTransport::send(message);
 }
 
 void DtlsSrtpTransport::incoming(message_ptr message) {

+ 0 - 1
src/dtlstransport.cpp

@@ -149,7 +149,6 @@ void DtlsTransport::postHandshake() {
 
 void DtlsTransport::runRecvLoop() {
 	const size_t maxMtu = 4096;
-
 	// Handshake loop
 	try {
 		changeState(State::Connecting);