diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d1f34c2a..ca0f12e52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,7 @@ set(LIBDATACHANNEL_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/rtcpsrreporter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/rtppacketizer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/h264rtppacketizer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/h264rtpdepacketizer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/nalunit.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/h265rtppacketizer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/h265nalunit.cpp @@ -110,6 +111,7 @@ set(LIBDATACHANNEL_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpsrreporter.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtppacketizer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h264rtppacketizer.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h264rtpdepacketizer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/nalunit.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h265rtppacketizer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h265nalunit.hpp diff --git a/include/rtc/h264rtpdepacketizer.hpp b/include/rtc/h264rtpdepacketizer.hpp new file mode 100644 index 000000000..663e459a4 --- /dev/null +++ b/include/rtc/h264rtpdepacketizer.hpp @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2020 Staz Modrzynski + * Copyright (c) 2020 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_H264_RTP_DEPACKETIZER_H +#define RTC_H264_RTP_DEPACKETIZER_H + +#if RTC_ENABLE_MEDIA + +#include "common.hpp" +#include "mediahandler.hpp" +#include "message.hpp" +#include "rtp.hpp" + +#include + +namespace rtc { + +/// RTP depacketization for H264 +class RTC_CPP_EXPORT H264RtpDepacketizer : public MediaHandler { +public: + H264RtpDepacketizer() = default; + virtual ~H264RtpDepacketizer() = default; + + void incoming(message_vector &messages, const message_callback &send) override; + +private: + std::vector mRtpBuffer; + + message_vector buildFrames(message_vector::iterator firstPkt, message_vector::iterator lastPkt); +}; + +} // namespace rtc + +#endif // RTC_ENABLE_MEDIA + +#endif /* RTC_H264_RTP_DEPACKETIZER_H */ diff --git a/include/rtc/nalunit.hpp b/include/rtc/nalunit.hpp index 030d8ea1e..0e7aa0bb6 100644 --- a/include/rtc/nalunit.hpp +++ b/include/rtc/nalunit.hpp @@ -25,6 +25,7 @@ struct RTC_CPP_EXPORT NalUnitHeader { bool forbiddenBit() const { return _first >> 7; } uint8_t nri() const { return _first >> 5 & 0x03; } + uint8_t idc() const { return _first & 0x60; } uint8_t unitType() const { return _first & 0x1F; } void setForbiddenBit(bool isSet) { _first = (_first & 0x7F) | (isSet << 7); } diff --git a/include/rtc/rtc.hpp b/include/rtc/rtc.hpp index ed10fa31f..dc7f62eac 100644 --- a/include/rtc/rtc.hpp +++ b/include/rtc/rtc.hpp @@ -30,6 +30,7 @@ // Media #include "av1rtppacketizer.hpp" #include "h264rtppacketizer.hpp" +#include "h264rtpdepacketizer.hpp" #include "h265rtppacketizer.hpp" #include "mediahandler.hpp" #include "plihandler.hpp" diff --git a/src/h264rtpdepacketizer.cpp b/src/h264rtpdepacketizer.cpp new file mode 100644 index 000000000..3e8cc1d00 --- /dev/null +++ b/src/h264rtpdepacketizer.cpp @@ -0,0 +1,139 @@ +/** + * Copyright (c) 2023 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#if RTC_ENABLE_MEDIA + +#include "h264rtpdepacketizer.hpp" +#include "nalunit.hpp" +#include "track.hpp" + +#include "impl/logcounter.hpp" + +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace rtc { + +const unsigned long stapaHeaderSize = 1; +const auto fuaHeaderSize = 2; + +const uint8_t naluTypeSTAPA = 24; +const uint8_t naluTypeFUA = 28; + +message_vector H264RtpDepacketizer::buildFrames(message_vector::iterator begin, + message_vector::iterator end) { + message_vector out = {}; + auto fua_buffer = std::vector{}; + + for (auto it = begin; it != end; it++) { + auto pkt = it->get(); + auto pktParsed = reinterpret_cast(pkt->data()); + auto headerSize = + sizeof(rtc::RtpHeader) + pktParsed->csrcCount() + pktParsed->getExtensionHeaderSize(); + auto nalUnitHeader = NalUnitHeader{std::to_integer(pkt->at(headerSize))}; + + if (fua_buffer.size() != 0 || nalUnitHeader.unitType() == naluTypeFUA) { + if (fua_buffer.size() == 0) { + fua_buffer.push_back(std::byte(0)); + } + + auto nalUnitFragmentHeader = + NalUnitFragmentHeader{std::to_integer(pkt->at(headerSize + 1))}; + + std::copy(pkt->begin() + headerSize + fuaHeaderSize, pkt->end(), + std::back_inserter(fua_buffer)); + + if (nalUnitFragmentHeader.isEnd()) { + fua_buffer.at(0) = + std::byte(nalUnitHeader.idc() | nalUnitFragmentHeader.unitType()); + + out.push_back(make_message(std::move(fua_buffer))); + fua_buffer.clear(); + } + } else if (nalUnitHeader.unitType() > 0 && nalUnitHeader.unitType() < 24) { + out.push_back(make_message(pkt->begin() + headerSize, pkt->end())); + } else if (nalUnitHeader.unitType() == naluTypeSTAPA) { + auto currOffset = stapaHeaderSize + headerSize; + + while (currOffset < pkt->size()) { + auto naluSize = + uint16_t(pkt->at(currOffset)) << 8 | uint8_t(pkt->at(currOffset + 1)); + + currOffset += 2; + + if (pkt->size() < currOffset + naluSize) { + throw std::runtime_error("STAP-A declared size is larger then buffer"); + } + + out.push_back( + make_message(pkt->begin() + currOffset, pkt->begin() + currOffset + naluSize)); + currOffset += naluSize; + } + + } else { + throw std::runtime_error("Unknown H264 RTP Packetization"); + } + } + + return out; +} + +void H264RtpDepacketizer::incoming(message_vector &messages, const message_callback &) { + for (auto message : messages) { + if (message->type == Message::Control) { + continue; // RTCP + } + + if (message->size() < sizeof(RtpHeader)) { + PLOG_VERBOSE << "RTP packet is too small, size=" << message->size(); + continue; + } + + mRtpBuffer.push_back(message); + } + + messages.clear(); + + while (mRtpBuffer.size() != 0) { + uint32_t current_timestamp = 0; + size_t packets_in_timestamp = 0; + + for (const auto &pkt : mRtpBuffer) { + auto p = reinterpret_cast(pkt->data()); + + if (current_timestamp == 0) { + current_timestamp = p->timestamp(); + } else if (current_timestamp != p->timestamp()) { + break; + } + + packets_in_timestamp++; + } + + if (packets_in_timestamp == mRtpBuffer.size()) { + break; + } + + auto begin = mRtpBuffer.begin(); + auto end = mRtpBuffer.begin() + (packets_in_timestamp - 1); + + auto frames = buildFrames(begin, end + 1); + messages.insert(messages.end(), frames.begin(), frames.end()); + mRtpBuffer.erase(mRtpBuffer.begin(), mRtpBuffer.begin() + packets_in_timestamp); + } +} + +} // namespace rtc + +#endif // RTC_ENABLE_MEDIA