diff --git a/CMakeLists.txt b/CMakeLists.txt index 3339e83..7cfb5be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,14 @@ set(LIBDATACHANNEL_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/track.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/processor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/capi.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/rtppacketizationconfig.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/rtcpsenderreportable.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/rtppacketizer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/opusrtppacketizer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/opuspacketizationhandler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/h264rtppacketizer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/nalunit.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/h264packetizationhandler.cpp ) set(LIBDATACHANNEL_WEBSOCKET_SOURCES @@ -95,6 +103,14 @@ set(LIBDATACHANNEL_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtp.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/track.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/websocket.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtppacketizationconfig.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpsenderreportable.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtppacketizer.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/opusrtppacketizer.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/opuspacketizationhandler.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h264rtppacketizer.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/nalunit.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h264packetizationhandler.hpp ) set(TESTS_SOURCES diff --git a/include/rtc/h264packetizationhandler.hpp b/include/rtc/h264packetizationhandler.hpp new file mode 100644 index 0000000..ba49df5 --- /dev/null +++ b/include/rtc/h264packetizationhandler.hpp @@ -0,0 +1,72 @@ +/* + * libdatachannel client example + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; If not, see . + */ + +#ifndef H264PacketizationHandler_hpp +#define H264PacketizationHandler_hpp + +#include "rtcp.hpp" +#include "h264rtppacketizer.hpp" +#include "rtcpsenderreportable.hpp" +#include "nalunit.hpp" + +#if RTC_ENABLE_MEDIA + +namespace rtc { + +/// Handler for H264 packetization +class H264PacketizationHandler: public RtcpHandler, public RTCPSenderReportable { + /// RTP packetizer for H264 + const std::shared_ptr packetizer; + + const uint16_t maximumFragmentSize; + + std::shared_ptr splitMessage(message_ptr message); +public: + /// Nalunit separator + enum class Separator { + LongStartSequence, // 0x00, 0x00, 0x00, 0x01 + ShortStartSequence, // 0x00, 0x00, 0x01 + StartSequence, // LongStartSequence or ShortStartSequence + Length // first 4 bytes is nal unit length + }; + + /// Construct handler for H264 packetization. + /// @param separator Nal units separator + /// @param packetizer RTP packetizer for h264 + H264PacketizationHandler(Separator separator, std::shared_ptr packetizer, uint16_t maximumFragmentSize = NalUnits::defaultMaximumFragmentSize); + + /// Returns message unchanged + /// @param ptr message + message_ptr incoming(message_ptr ptr) override; + + /// Returns packetized message if message type is binary + /// @note NAL units in `ptr` message must be separated by `separator` given in constructor + /// @note If message generates multiple rtp packets, all but last are send using `outgoingCallback`. It is your responsibility to send last packet. + /// @param ptr message containing all NAL units for current timestamp (one sample) + /// @return last packet + message_ptr outgoing(message_ptr ptr) override; +private: + /// Separator + const Separator separator; +}; + +} // namespace + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* H264PacketizationHandler_hpp */ diff --git a/include/rtc/h264rtppacketizer.hpp b/include/rtc/h264rtppacketizer.hpp new file mode 100644 index 0000000..6227b8b --- /dev/null +++ b/include/rtc/h264rtppacketizer.hpp @@ -0,0 +1,45 @@ +/* + * libdatachannel streamer example + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; If not, see . + */ + +#ifndef H264RTPPacketizer_hpp +#define H264RTPPacketizer_hpp + +#include "rtppacketizer.hpp" + +#if RTC_ENABLE_MEDIA + +namespace rtc { + +/// RTP packetization of h264 payload +class H264RTPPacketizer: public rtc::RTPPacketizer { + +public: + /// Default clock rate for H264 in RTP + static const auto defaultClockRate = 90 * 1000; + + /// Constructs h264 payload packetizer with given RTP configuration. + /// @note RTP configuration is used in packetization process which may change some configuration properties such as sequence number. + /// @param rtpConfig RTP configuration + H264RTPPacketizer(std::shared_ptr rtpConfig); +}; + +} // namespace + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* H264RTPPacketizer_hpp */ diff --git a/include/rtc/nalunit.hpp b/include/rtc/nalunit.hpp new file mode 100644 index 0000000..76a21b6 --- /dev/null +++ b/include/rtc/nalunit.hpp @@ -0,0 +1,155 @@ +/* + * libdatachannel streamer example + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; If not, see . + */ + +#ifndef NalUnit_hpp +#define NalUnit_hpp + +#include "include.hpp" + +#if RTC_ENABLE_MEDIA + +namespace rtc { + +#pragma pack(push, 1) + +/// Nalu header +struct NalUnitHeader { + bool forbiddenBit() { return _first >> 7; } + uint8_t nri() { return _first >> 5 & 0x03; } + uint8_t unitType() { return _first & 0x1F; } + + void setForbiddenBit(bool isSet) { _first = (_first & 0x7F) | (isSet << 7); } + void setNRI(uint8_t nri) { _first = (_first & 0x9F) | ((nri & 0x03) << 5); } + void setUnitType(uint8_t type) { _first = (_first &0xE0) | (type & 0x1F); } +private: + uint8_t _first = 0; +}; + +/// Nalu fragment header +struct NalUnitFragmentHeader { + bool isStart() { return _first >> 7; } + bool reservedBit6() { return (_first >> 6) & 0x01; } + bool isEnd() { return (_first >> 5) & 0x01; } + uint8_t unitType() { return _first & 0x1F; } + + void setStart(bool isSet) { _first = (_first & 0x7F) | (isSet << 7); } + void setEnd(bool isSet) { _first = (_first & 0xDF) | (isSet << 6); } + void setReservedBit6(bool isSet) { _first = (_first & 0xBF) | (isSet << 5); } + void setUnitType(uint8_t type) { _first = (_first &0xE0) | (type & 0x1F); } +private: + uint8_t _first = 0; +}; + +#pragma pack(pop) + +/// Nal unit +struct NalUnit: rtc::binary { + NalUnit(const NalUnit &unit) = default; + NalUnit(size_t size, bool includingHeader = true): rtc::binary(size + (includingHeader ? 0 : 1)) { } + + template + NalUnit(Iterator begin_, Iterator end_): rtc::binary(begin_, end_) { } + + NalUnit(rtc::binary &&data) : rtc::binary(std::move(data)) { } + + bool forbiddenBit() { return header()->forbiddenBit(); } + uint8_t nri() { return header()->nri(); } + uint8_t unitType() { return header()->unitType(); } + rtc::binary payload() { + assert(size() >= 1); + return {begin() + 1, end()}; + } + + void setForbiddenBit(bool isSet) { header()->setForbiddenBit(isSet); } + void setNRI(uint8_t nri) { header()->setNRI(nri); } + void setUnitType(uint8_t type) { header()->setUnitType(type); } + void setPayload(rtc::binary payload) { + assert(size() >= 1); + erase(begin() + 1, end()); + insert(end(), payload.begin(), payload.end()); + } + +protected: + NalUnitHeader * header() { + assert(size() >= 1); + return (NalUnitHeader *) data(); + } +}; + +/// Nal unit fragment A +struct NalUnitFragmentA: NalUnit { + enum class FragmentType { + Start, + Middle, + End + }; + + NalUnitFragmentA(FragmentType type, bool forbiddenBit, uint8_t nri, uint8_t unitType, rtc::binary data); + + static std::vector fragmentsFrom(NalUnit nalu, uint16_t maximumFragmentSize); + + uint8_t unitType() { return fragmentHeader()->unitType(); } + + rtc::binary payload() { + assert(size() >= 2); + return {begin() + 2, end()}; + } + + FragmentType type() { + if(fragmentHeader()->isStart()) { + return FragmentType::Start; + } else if(fragmentHeader()->isEnd()) { + return FragmentType::End; + } else { + return FragmentType::Middle; + } + } + + void setUnitType(uint8_t type) { fragmentHeader()->setUnitType(type); } + + void setPayload(rtc::binary payload) { + assert(size() >= 2); + erase(begin() + 2, end()); + insert(end(), payload.begin(), payload.end()); + } + + void setFragmentType(FragmentType type); + +protected: + NalUnitHeader * fragmentIndicator() { + return (NalUnitHeader *) data(); + } + + NalUnitFragmentHeader * fragmentHeader() { + return (NalUnitFragmentHeader *) fragmentIndicator() + 1; + } + + const uint8_t nal_type_fu_A = 28; +}; + +class NalUnits: public std::vector { +public: + static const uint16_t defaultMaximumFragmentSize = 1100; + std::vector generateFragments(uint16_t maximumFragmentSize = NalUnits::defaultMaximumFragmentSize); +}; + +} // namespace + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* NalUnit_hpp */ diff --git a/include/rtc/opuspacketizationhandler.hpp b/include/rtc/opuspacketizationhandler.hpp new file mode 100644 index 0000000..4f1d5d5 --- /dev/null +++ b/include/rtc/opuspacketizationhandler.hpp @@ -0,0 +1,52 @@ +/* + * libdatachannel client example + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; If not, see . + */ + +#ifndef OpusPacketizationHandler_hpp +#define OpusPacketizationHandler_hpp + +#include "rtcpsenderreportable.hpp" +#include "opusrtppacketizer.hpp" +#include "rtcp.hpp" + +#if RTC_ENABLE_MEDIA + +namespace rtc { + +/// Handler for opus packetization +class OpusPacketizationHandler: public RtcpHandler, public RTCPSenderReportable { + /// RTP packetizer for opus + const std::shared_ptr packetizer; + +public: + /// Construct handler for opus packetization. + /// @param packetizer RTP packetizer for opus + OpusPacketizationHandler(std::shared_ptr packetizer); + + /// Returns message unchanged + /// @param ptr message + message_ptr incoming(message_ptr ptr) override; + /// Returns packetized message if message type is binary + /// @param ptr message + message_ptr outgoing(message_ptr ptr) override; +}; + +} // namespace + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* OpusPacketizationHandler_hpp */ diff --git a/include/rtc/opusrtppacketizer.hpp b/include/rtc/opusrtppacketizer.hpp new file mode 100644 index 0000000..bfefb8d --- /dev/null +++ b/include/rtc/opusrtppacketizer.hpp @@ -0,0 +1,51 @@ +/* + * libdatachannel streamer example + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; If not, see . + */ + +#ifndef OpusRTPPacketizer_hpp +#define OpusRTPPacketizer_hpp + +#include "rtppacketizer.hpp" + +#if RTC_ENABLE_MEDIA + +namespace rtc { + +/// RTP packetizer for opus +class OpusRTPPacketizer: public rtc::RTPPacketizer { + +public: + /// default clock rate used in opus RTP communication + static const uint32_t defaultClockRate = 48 * 1000; + + /// Constructs opus packetizer with given RTP configuration. + /// @note RTP configuration is used in packetization process which may change some configuration properties such as sequence number. + /// @param rtpConfig RTP configuration + OpusRTPPacketizer(std::shared_ptr rtpConfig); + + /// Creates RTP packet for given payload based on `rtpConfig`. + /// @note This function increase sequence number after packetization. + /// @param payload RTP payload + /// @param setMark This needs to be `false` for all RTP packets with opus payload + rtc::message_ptr packetize(rtc::binary payload, bool setMark) override; +}; + +} // namespace + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* OpusRTPPacketizer_hpp */ diff --git a/include/rtc/rtc.hpp b/include/rtc/rtc.hpp index 261e4db..7ad4e01 100644 --- a/include/rtc/rtc.hpp +++ b/include/rtc/rtc.hpp @@ -25,5 +25,13 @@ #include "peerconnection.hpp" #include "websocket.hpp" +#if RTC_ENABLE_MEDIA + +// opus/h264 streaming +#include "opuspacketizationhandler.hpp" +#include "h264packetizationhandler.hpp" + +#endif /* RTC_ENABLE_MEDIA */ + // C API #include "rtc.h" diff --git a/include/rtc/rtcpsenderreportable.hpp b/include/rtc/rtcpsenderreportable.hpp new file mode 100644 index 0000000..69c243c --- /dev/null +++ b/include/rtc/rtcpsenderreportable.hpp @@ -0,0 +1,87 @@ +/* + * libdatachannel streamer example + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; If not, see . + */ + +#ifndef RTCPSenderReporter_hpp +#define RTCPSenderReporter_hpp + +#include "message.hpp" +#include "rtppacketizationconfig.hpp" + +#if RTC_ENABLE_MEDIA + +namespace rtc { + +/// Class for sending RTCP SR +class RTCPSenderReportable { + bool needsToReport = false; + + uint32_t packetCount = 0; + uint32_t payloadOctets = 0; + double timeOffset = 0; + + uint32_t _previousReportedTimestamp = 0; + + void addToReport(RTP * rtp, uint32_t rtpSize); + message_ptr getSenderReport(uint32_t timestamp); +protected: + /// Outgoing callback for sender reports + synchronized_callback senderReportOutgoingCallback; +public: + static uint64_t secondsToNTP(double seconds); + + /// Timestamp of previous sender report + const uint32_t & previousReportedTimestamp = _previousReportedTimestamp; + + /// RTP configuration + const std::shared_ptr rtpConfig; + + RTCPSenderReportable(std::shared_ptr rtpConfig); + + /// Set `needsToReport` flag. Sender report will be sent before next RTP packet with same timestamp. + void setNeedsToReport(); + + /// Set offset to compute NTS for RTCP SR packets. Offset represents relation between real start time and timestamp of the stream in RTP packets + /// @note `time_offset = rtpConfig->startTime_s - rtpConfig->timestampToSeconds(rtpConfig->timestamp)` + void startRecording(); + + /// Send RTCP SR with given timestamp + /// @param timestamp timestamp of the RTCP SR + void sendReport(uint32_t timestamp); + +protected: + /// Calls given block with function for statistics. Sends RTCP SR packet with current timestamp before `block` call if `needs_to_report` flag is true. + /// @param block Block of code to run. This block has function for rtp stats recording. + template + T withStatsRecording(std::function)> block) { + if (needsToReport) { + sendReport(rtpConfig->timestamp); + needsToReport = false; + } + auto result = block([this](message_ptr _rtp) { + auto rtp = reinterpret_cast(_rtp->data()); + this->addToReport(rtp, _rtp->size()); + }); + return result; + } +}; + +} // namespace + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* RTCPSenderReporter_hpp */ diff --git a/include/rtc/rtppacketizationconfig.hpp b/include/rtc/rtppacketizationconfig.hpp new file mode 100644 index 0000000..89ab128 --- /dev/null +++ b/include/rtc/rtppacketizationconfig.hpp @@ -0,0 +1,91 @@ +/* + * libdatachannel streamer example + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; If not, see . + */ + +#ifndef RTPPacketizationConfig_hpp +#define RTPPacketizationConfig_hpp + +#include "rtp.hpp" + +#if RTC_ENABLE_MEDIA + +namespace rtc { + +/// RTP configuration used in packetization process +struct RTPPacketizationConfig { +private: + uint32_t _startTimestamp = 0; + double _startTime_s = 0; + RTPPacketizationConfig(const RTPPacketizationConfig&) = delete; +public: + const SSRC ssrc; + const std::string cname; + const uint8_t payloadType; + const uint32_t clockRate; + const double & startTime_s = _startTime_s; + const uint32_t & startTimestamp = _startTimestamp; + + /// current sequence number + uint16_t sequenceNumber; + /// current timestamp + uint32_t timestamp; + + enum class EpochStart: unsigned long long { + T1970 = 2208988800, // number of seconds between 1970 and 1900 + T1900 = 0 + }; + + /// Creates relation between time and timestamp mapping given start time and start timestamp + /// @param startTime_s Start time of the stream + /// @param epochStart Type of used epoch + /// @param startTimestamp Corresponding timestamp for given start time (current timestamp will be used if value is nullopt) + void setStartTime(double startTime_s, EpochStart epochStart, std::optional startTimestamp = std::nullopt); + + + /// Construct RTP configuration used in packetization process + /// @param ssrc SSRC of source + /// @param cname CNAME of source + /// @param payloadType Payload type of source + /// @param clockRate Clock rate of source used in timestamps + /// @param sequenceNumber Initial sequence number of RTP packets (random number is choosed if nullopt) + /// @param timestamp Initial timastamp of RTP packets (random number is choosed if nullopt) + RTPPacketizationConfig(SSRC ssrc, std::string cname, uint8_t payloadType, uint32_t clockRate, std::optional sequenceNumber = std::nullopt, std::optional timestamp = std::nullopt); + + /// Convert timestamp to seconds + /// @param timestamp Timestamp + /// @param clockRate Clock rate for timestamp calculation + static double getSecondsFromTimestamp(uint32_t timestamp, uint32_t clockRate); + + /// Convert timestamp to seconds + /// @param timestamp Timestamp + double timestampToSeconds(uint32_t timestamp); + + /// Convert seconds to timestamp + /// @param seconds Number of seconds + /// @param clockRate Clock rate for timestamp calculation + static uint32_t getTimestampFromSeconds(double seconds, uint32_t clockRate); + + /// Convert seconds to timestamp + /// @param seconds Number of seconds + uint32_t secondsToTimestamp(double seconds); +}; + +} // namespace + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* RTPPacketizationConfig_hpp */ diff --git a/include/rtc/rtppacketizer.hpp b/include/rtc/rtppacketizer.hpp new file mode 100644 index 0000000..154a562 --- /dev/null +++ b/include/rtc/rtppacketizer.hpp @@ -0,0 +1,52 @@ +/* + * libdatachannel streamer example + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; If not, see . + */ + +#ifndef RTPPacketizer_hpp +#define RTPPacketizer_hpp + +#include "rtppacketizationconfig.hpp" +#include "message.hpp" + +#if RTC_ENABLE_MEDIA + +namespace rtc { + +/// Class responsizble for rtp packetization +class RTPPacketizer { + static const auto rtpHeaderSize = 12; +public: + // rtp configuration + const std::shared_ptr rtpConfig; + + /// Constructs packetizer with given RTP configuration. + /// @note RTP configuration is used in packetization process which may change some configuration properties such as sequence number. + /// @param rtpConfig RTP configuration + RTPPacketizer(std::shared_ptr rtpConfig); + + /// Creates RTP packet for given payload based on `rtpConfig`. + /// @note This function increase sequence number after packetization. + /// @param payload RTP payload + /// @param setMark Set marker flag in RTP packet if true + virtual message_ptr packetize(binary payload, bool setMark); +}; + +} // namespace + +#endif /* RTC_ENABLE_MEDIA */ + +#endif /* RTPPacketizer_hpp */ diff --git a/src/h264packetizationhandler.cpp b/src/h264packetizationhandler.cpp new file mode 100644 index 0000000..9351d69 --- /dev/null +++ b/src/h264packetizationhandler.cpp @@ -0,0 +1,164 @@ +/* + * libdatachannel client example + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; If not, see . + */ + +#include "h264packetizationhandler.hpp" + +#if RTC_ENABLE_MEDIA + +using namespace rtc; +using namespace std; + +typedef enum { + NUSM_noMatch, + NUSM_firstZero, + NUSM_secondZero, + NUSM_thirdZero, + NUSM_shortMatch, + NUSM_longMatch +} NalUnitStartSequenceMatch; + +NalUnitStartSequenceMatch StartSequenceMatchSucc(NalUnitStartSequenceMatch match, byte _byte, H264PacketizationHandler::Separator separator) { + assert(separator != H264PacketizationHandler::Separator::Length); + auto byte = (uint8_t) _byte; + auto detectShort = separator == H264PacketizationHandler::Separator::ShortStartSequence || separator == H264PacketizationHandler::Separator::StartSequence; + auto detectLong = separator == H264PacketizationHandler::Separator::LongStartSequence || separator == H264PacketizationHandler::Separator::StartSequence; + switch (match) { + case NUSM_noMatch: + if (byte == 0x00) { + return NUSM_firstZero; + } + break; + case NUSM_firstZero: + if (byte == 0x00) { + return NUSM_secondZero; + } + break; + case NUSM_secondZero: + if (byte == 0x00 && detectLong) { + return NUSM_thirdZero; + } else if (byte == 0x01 && detectShort) { + return NUSM_shortMatch; + } + break; + case NUSM_thirdZero: + if (byte == 0x01 && detectLong) { + return NUSM_longMatch; + } + break; + case NUSM_shortMatch: + return NUSM_shortMatch; + case NUSM_longMatch: + return NUSM_longMatch; + } + return NUSM_noMatch; +} + +message_ptr H264PacketizationHandler::incoming(message_ptr ptr) { + return ptr; +} + +shared_ptr H264PacketizationHandler::splitMessage(rtc::message_ptr message) { + auto nalus = make_shared(); + if (separator == Separator::Length) { + unsigned long long index = 0; + while (index < message->size()) { + assert(index + 4 < message->size()); + if (index + 4 >= message->size()) { + LOG_WARNING << "Invalid NAL Unit data (incomplete length), ignoring!"; + break; + } + auto lengthPtr = (uint32_t *) (message->data() + index); + uint32_t length = ntohl(*lengthPtr); + auto naluStartIndex = index + 4; + auto naluEndIndex = naluStartIndex + length; + + assert(naluEndIndex <= message->size()); + if (naluEndIndex > message->size()) { + LOG_WARNING << "Invalid NAL Unit data (incomplete unit), ignoring!"; + break; + } + nalus->push_back(NalUnit(message->begin() + naluStartIndex, message->begin() + naluEndIndex)); + index = naluEndIndex; + } + } else { + NalUnitStartSequenceMatch match = NUSM_noMatch; + unsigned long long index = 0; + while (index < message->size()) { + match = StartSequenceMatchSucc(match, (*message)[index++], separator); + if (match == NUSM_longMatch || match == NUSM_shortMatch) { + match = NUSM_noMatch; + break; + } + } + index++; + unsigned long long naluStartIndex = index; + + while (index < message->size()) { + match = StartSequenceMatchSucc(match, (*message)[index], separator); + if (match == NUSM_longMatch || match == NUSM_shortMatch) { + auto sequenceLength = match == NUSM_longMatch ? 4 : 3; + unsigned long long naluEndIndex = index - sequenceLength; + match = NUSM_noMatch; + nalus->push_back(NalUnit(message->begin() + naluStartIndex, message->begin() + naluEndIndex + 1)); + naluStartIndex = index + 1; + } + index++; + } + nalus->push_back(NalUnit(message->begin() + naluStartIndex, message->end())); + } + return nalus; +} + +message_ptr H264PacketizationHandler::outgoing(message_ptr ptr) { + if (ptr->type == Message::Binary) { + auto nalus = splitMessage(ptr); + auto fragments = nalus->generateFragments(maximumFragmentSize); + + auto lastPacket = withStatsRecording([fragments, this](function addToReport) { + for(unsigned long long index = 0; index < fragments.size() - 1; index++) { + auto packet = packetizer->packetize(fragments[index], false); + + addToReport(packet); + + outgoingCallback(std::move(packet)); + } + // packet is last, marker must be set + auto lastPacket = packetizer->packetize(fragments[fragments.size() - 1], true); + addToReport(lastPacket); + return lastPacket; + }); + return lastPacket; + } + return ptr; +} + +H264PacketizationHandler::H264PacketizationHandler(Separator separator, + std::shared_ptr packetizer, + uint16_t maximumFragmentSize): + RtcpHandler(), + rtc::RTCPSenderReportable(packetizer->rtpConfig), + packetizer(packetizer), + maximumFragmentSize(maximumFragmentSize), + separator(separator) { + + senderReportOutgoingCallback = [this](message_ptr msg) { + outgoingCallback(msg); + }; +} + +#endif /* RTC_ENABLE_MEDIA */ diff --git a/src/h264rtppacketizer.cpp b/src/h264rtppacketizer.cpp new file mode 100644 index 0000000..2d3f7f3 --- /dev/null +++ b/src/h264rtppacketizer.cpp @@ -0,0 +1,28 @@ +/* + * libdatachannel streamer example + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; If not, see . + */ + +#include "h264rtppacketizer.hpp" + +#if RTC_ENABLE_MEDIA + +using namespace std; +using namespace rtc; + +H264RTPPacketizer::H264RTPPacketizer(std::shared_ptr rtpConfig): RTPPacketizer(rtpConfig) { } + +#endif /* RTC_ENABLE_MEDIA */ diff --git a/src/nalunit.cpp b/src/nalunit.cpp new file mode 100644 index 0000000..9430f9a --- /dev/null +++ b/src/nalunit.cpp @@ -0,0 +1,103 @@ +/* + * libdatachannel streamer example + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; If not, see . + */ + +#include "nalunit.hpp" + +#if RTC_ENABLE_MEDIA + +using namespace std; +using namespace rtc; + +NalUnitFragmentA::NalUnitFragmentA(FragmentType type, bool forbiddenBit, uint8_t nri, uint8_t unitType, binary data): NalUnit(data.size() + 2) { + setForbiddenBit(forbiddenBit); + setNRI(nri); + fragmentIndicator()->setUnitType(NalUnitFragmentA::nal_type_fu_A); + setFragmentType(type); + setUnitType(unitType); + copy(data.begin(), data.end(), begin() + 2); +} + +vector NalUnitFragmentA::fragmentsFrom(NalUnit nalu, uint16_t maximumFragmentSize) { + assert(nalu.size() > maximumFragmentSize); + if (nalu.size() <= maximumFragmentSize) { + // we need to change `maximum_fragment_size` to have at least two fragments + maximumFragmentSize = nalu.size() / 2; + } + auto fragments_count = ceil(double(nalu.size()) / maximumFragmentSize); + maximumFragmentSize = ceil(nalu.size() / fragments_count); + + // 2 bytes for FU indicator and FU header + maximumFragmentSize -= 2; + auto f = nalu.forbiddenBit(); + uint8_t nri = nalu.nri() & 0x03; + uint8_t naluType = nalu.unitType() & 0x1F; + auto payload = nalu.payload(); + vector result{}; + uint64_t offset = 0; + while (offset < payload.size()) { + vector fragmentData; + FragmentType fragmentType; + if (offset == 0) { + fragmentType = FragmentType::Start; + } else if (offset + maximumFragmentSize < payload.size()) { + fragmentType = FragmentType::Middle; + } else { + if (offset + maximumFragmentSize > payload.size()) { + maximumFragmentSize = payload.size() - offset; + } + fragmentType = FragmentType::End; + } + fragmentData = {payload.begin() + offset, payload.begin() + offset + maximumFragmentSize}; + NalUnitFragmentA fragment{fragmentType, f, nri, naluType, fragmentData}; + result.push_back(fragment); + offset += maximumFragmentSize; + } + return result; +} + +void NalUnitFragmentA::setFragmentType(FragmentType type) { + fragmentHeader()->setReservedBit6(false); + switch (type) { + case FragmentType::Start: + fragmentHeader()->setStart(true); + fragmentHeader()->setEnd(false); + break; + case FragmentType::End: + fragmentHeader()->setStart(false); + fragmentHeader()->setEnd(true); + break; + default: + fragmentHeader()->setStart(false); + fragmentHeader()->setEnd(false); + } +} + +vector NalUnits::generateFragments(uint16_t maximumFragmentSize) { + vector result{}; + for (auto nalu: *this) { + if (nalu.size() > maximumFragmentSize) { + std::vector fragments = NalUnitFragmentA::fragmentsFrom(nalu, maximumFragmentSize); + result.insert(result.end(), fragments.begin(), fragments.end()); + } else { + result.push_back(nalu); + } + } + return result; +} + +#endif /* RTC_ENABLE_MEDIA */ diff --git a/src/opuspacketizationhandler.cpp b/src/opuspacketizationhandler.cpp new file mode 100644 index 0000000..362c210 --- /dev/null +++ b/src/opuspacketizationhandler.cpp @@ -0,0 +1,46 @@ +/* + * libdatachannel client example + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; If not, see . + */ + +#include "opuspacketizationhandler.hpp" + +#if RTC_ENABLE_MEDIA + +using namespace rtc; + +OpusPacketizationHandler::OpusPacketizationHandler(std::shared_ptr packetizer): RtcpHandler(), RTCPSenderReportable(packetizer->rtpConfig), packetizer(packetizer) { + senderReportOutgoingCallback = [this](message_ptr msg) { + outgoingCallback(msg); + }; +} + +message_ptr OpusPacketizationHandler::incoming(message_ptr ptr) { + return ptr; +} + +message_ptr OpusPacketizationHandler::outgoing(message_ptr ptr) { + if (ptr->type == Message::Binary) { + return withStatsRecording([this, ptr](std::function addToReport) { + auto rtp = packetizer->packetize(*ptr, false); + addToReport(rtp); + return rtp; + }); + } + return ptr; +} + +#endif /* RTC_ENABLE_MEDIA */ diff --git a/src/opusrtppacketizer.cpp b/src/opusrtppacketizer.cpp new file mode 100644 index 0000000..c7207ec --- /dev/null +++ b/src/opusrtppacketizer.cpp @@ -0,0 +1,33 @@ +/* + * libdatachannel streamer example + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; If not, see . + */ + +#include "opusrtppacketizer.hpp" + +#if RTC_ENABLE_MEDIA + +using namespace std; +using namespace rtc; + +OpusRTPPacketizer::OpusRTPPacketizer(std::shared_ptr rtpConfig): RTPPacketizer(rtpConfig) { } + +message_ptr OpusRTPPacketizer::packetize(binary payload, bool setMark) { + assert(!setMark); + return RTPPacketizer::packetize(payload, false); +} + +#endif /* RTC_ENABLE_MEDIA */ diff --git a/src/rtcpsenderreportable.cpp b/src/rtcpsenderreportable.cpp new file mode 100644 index 0000000..7e382fe --- /dev/null +++ b/src/rtcpsenderreportable.cpp @@ -0,0 +1,74 @@ +/* + * libdatachannel streamer example + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; If not, see . + */ + +#include "rtcpsenderreportable.hpp" + +#if RTC_ENABLE_MEDIA + +using namespace rtc; +using namespace std; + +void RTCPSenderReportable::startRecording() { + _previousReportedTimestamp = rtpConfig->timestamp; + timeOffset = rtpConfig->startTime_s - rtpConfig->timestampToSeconds(rtpConfig->timestamp); +} + +void RTCPSenderReportable::sendReport(uint32_t timestamp) { + auto sr = getSenderReport(timestamp); + _previousReportedTimestamp = timestamp; + senderReportOutgoingCallback(move(sr)); +} + +void RTCPSenderReportable::addToReport(RTP * rtp, uint32_t rtpSize) { + packetCount += 1; + assert(!rtp->padding()); + payloadOctets += rtpSize - rtp->getSize(); +} + +RTCPSenderReportable::RTCPSenderReportable(std::shared_ptr rtpConfig): rtpConfig(rtpConfig) { } + +uint64_t RTCPSenderReportable::secondsToNTP(double seconds) { + return std::round(seconds * double(uint64_t(1) << 32)); +} + +void RTCPSenderReportable::setNeedsToReport() { + needsToReport = true; +} + +message_ptr RTCPSenderReportable::getSenderReport(uint32_t timestamp) { + auto srSize = RTCP_SR::size(0); + auto msg = make_message(srSize + RTCP_SDES::size({uint8_t(rtpConfig->cname.size())}), Message::Type::Control); + auto sr = reinterpret_cast(msg->data()); + auto timestamp_s = rtpConfig->timestampToSeconds(timestamp); + auto currentTime = timeOffset + timestamp_s; + sr->setNtpTimestamp(secondsToNTP(currentTime)); + sr->setRtpTimestamp(timestamp); + sr->setPacketCount(packetCount); + sr->setOctetCount(payloadOctets); + sr->preparePacket(rtpConfig->ssrc, 0); + + auto sdes = reinterpret_cast(msg->data() + srSize); + auto chunk = sdes->getChunk(0); + chunk->setSSRC(rtpConfig->ssrc); + chunk->type = 1; + chunk->setText(rtpConfig->cname); + sdes->preparePacket(1); + return msg; +} + +#endif /* RTC_ENABLE_MEDIA */ diff --git a/src/rtppacketizationconfig.cpp b/src/rtppacketizationconfig.cpp new file mode 100644 index 0000000..5b59332 --- /dev/null +++ b/src/rtppacketizationconfig.cpp @@ -0,0 +1,73 @@ +/* + * libdatachannel streamer example + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; If not, see . + */ + +#include "rtppacketizationconfig.hpp" + +#if RTC_ENABLE_MEDIA + +using namespace rtc; +using namespace std; + +RTPPacketizationConfig::RTPPacketizationConfig(SSRC ssrc, + string cname, + uint8_t payloadType, + uint32_t clockRate, + optional sequenceNumber, + optional timestamp): ssrc(ssrc), cname(cname), payloadType(payloadType), clockRate(clockRate) { + assert(clockRate > 0); + srand((unsigned)time(NULL)); + if (sequenceNumber.has_value()) { + this->sequenceNumber = sequenceNumber.value(); + } else { + this->sequenceNumber = rand(); + } + if (timestamp.has_value()) { + this->timestamp = timestamp.value(); + } else { + this->timestamp = rand(); + } + this->_startTimestamp = this->timestamp; +} + +void RTPPacketizationConfig::setStartTime(double startTime_s, EpochStart epochStart, std::optional startTimestamp) { + this->_startTime_s = startTime_s + static_cast(epochStart); + if (startTimestamp.has_value()) { + this->_startTimestamp = startTimestamp.value(); + timestamp = this->startTimestamp; + } else { + this->_startTimestamp = timestamp; + } +} + +double RTPPacketizationConfig::getSecondsFromTimestamp(uint32_t timestamp, uint32_t clockRate) { + return double(timestamp) / double(clockRate); +} + +double RTPPacketizationConfig::timestampToSeconds(uint32_t timestamp) { + return RTPPacketizationConfig::getSecondsFromTimestamp(timestamp, clockRate); +} + +uint32_t RTPPacketizationConfig::getTimestampFromSeconds(double seconds, uint32_t clockRate) { + return uint32_t(seconds * clockRate); +} + +uint32_t RTPPacketizationConfig::secondsToTimestamp(double seconds) { + return RTPPacketizationConfig::getTimestampFromSeconds(seconds, clockRate); +} + +#endif /* RTC_ENABLE_MEDIA */ diff --git a/src/rtppacketizer.cpp b/src/rtppacketizer.cpp new file mode 100644 index 0000000..3e55ff2 --- /dev/null +++ b/src/rtppacketizer.cpp @@ -0,0 +1,44 @@ +/* + * libdatachannel streamer example + * Copyright (c) 2020 Filip Klembara (in2core) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; If not, see . + */ + +#include "rtppacketizer.hpp" + +#if RTC_ENABLE_MEDIA + +using namespace std; +using namespace rtc; + +RTPPacketizer::RTPPacketizer(shared_ptr rtpConfig): rtpConfig(rtpConfig) { } + +message_ptr RTPPacketizer::packetize(binary payload, bool setMark) { + auto msg = make_message(rtpHeaderSize + payload.size()); + auto * rtp = (RTP *)msg->data(); + rtp->setPayloadType(rtpConfig->payloadType); + // increase sequence number + rtp->setSeqNumber(rtpConfig->sequenceNumber++); + rtp->setTimestamp(rtpConfig->timestamp); + rtp->setSsrc(rtpConfig->ssrc); + if (setMark) { + rtp->setMarker(true); + } + rtp->preparePacket(); + copy(payload.begin(), payload.end(), msg->begin() + rtpHeaderSize); + return msg; +} + +#endif /* RTC_ENABLE_MEDIA */