ZLMediaKit/webrtc/RtpExt.cpp
2021-05-07 17:52:33 +08:00

501 lines
20 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
*
* Use of this source code is governed by MIT license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "RtpExt.h"
#include "Sdp.h"
#if defined(_WIN32)
#pragma pack(push, 1)
#endif // defined(_WIN32)
//https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01
//https://tools.ietf.org/html/rfc5285
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | 0xBE | 0xDE | length=3 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | L=0 | data | ID | L=1 | data...
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// ...data | 0 (pad) | 0 (pad) | ID | L=3 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | data |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
class RtpExtOneByte {
public:
static constexpr uint16_t kMinSize = 1;
size_t getSize() const;
uint8_t getId() const;
uint8_t* getData();
private:
#if __BYTE_ORDER == __BIG_ENDIAN
uint8_t id: 4;
uint8_t len: 4;
#else
uint8_t len: 4;
uint8_t id: 4;
#endif
uint8_t data[1];
} PACKED;
//0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | 0x100 |appbits| length=3 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | L=0 | ID | L=1 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | data | 0 (pad) | ID | L=4 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | data |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
class RtpExtTwoByte {
public:
static constexpr uint16_t kMinSize = 2;
size_t getSize() const;
uint8_t getId() const;
uint8_t* getData();
private:
uint8_t id;
uint8_t len;
uint8_t data[1];
} PACKED;
#if defined(_WIN32)
#pragma pack(pop)
#endif // defined(_WIN32)
//////////////////////////////////////////////////////////////////
size_t RtpExtOneByte::getSize() const {
return len + 1;
}
uint8_t RtpExtOneByte::getId() const {
return id;
}
uint8_t *RtpExtOneByte::getData() {
return data;
}
//////////////////////////////////////////////////////////////////
size_t RtpExtTwoByte::getSize() const {
return len;
}
uint8_t RtpExtTwoByte::getId() const {
return id;
}
uint8_t *RtpExtTwoByte::getData() {
return data;
}
//////////////////////////////////////////////////////////////////
static constexpr uint16_t kOneByteHeader = 0xBEDE;
static constexpr uint16_t kTwoByteHeader = 0x1000;
static RtpExtType getExtTypeById(uint8_t id, const RtcMedia &media){
auto it = media.extmap.find(id);
if (it == media.extmap.end()) {
return RtpExtType::padding;
}
return RtpExt::getExtType(it->second.ext);
}
template<typename Type>
static void appendExt( map<RtpExtType, RtpExt> &ret,const RtcMedia &media, uint8_t *ptr, const uint8_t *end){
while (ptr < end) {
auto ext = reinterpret_cast<Type *>(ptr);
if (ext->getId() == (uint8_t) RtpExtType::padding) {
//padding忽略
++ptr;
continue;
}
//15类型的rtp ext为保留
CHECK(ext->getId() < (uint8_t) RtpExtType::reserved);
CHECK(reinterpret_cast<uint8_t *>(ext) + Type::kMinSize <= end);
CHECK(ext->getData() + ext->getSize() <= end);
auto type = getExtTypeById(ext->getId(), media);
ret.emplace(type, RtpExt(type, ext->getId(), reinterpret_cast<char *>(ext->getData()), ext->getSize()));
ptr += Type::kMinSize + ext->getSize();
}
}
map<RtpExtType/*type*/, RtpExt/*data*/> RtpExt::getExtValue(const RtpHeader *header, const RtcMedia &media) {
map<RtpExtType, RtpExt> ret;
assert(header);
auto ext_size = header->getExtSize();
if (!ext_size) {
return ret;
}
auto reserved = header->getExtReserved();
auto ptr = const_cast<RtpHeader *>(header)->getExtData();
auto end = ptr + ext_size;
if (reserved == kOneByteHeader) {
appendExt<RtpExtOneByte>(ret, media, ptr, end);
return ret;
}
if ((reserved & 0xFFF0) >> 4 == kTwoByteHeader) {
appendExt<RtpExtTwoByte>(ret, media, ptr, end);
return ret;
}
return ret;
}
#define RTP_EXT_MAP(XX) \
XX(ssrc_audio_level, "urn:ietf:params:rtp-hdrext:ssrc-audio-level") \
XX(abs_send_time, "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time") \
XX(transport_cc, "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01") \
XX(sdes_mid, "urn:ietf:params:rtp-hdrext:sdes:mid") \
XX(sdes_rtp_stream_id, "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id") \
XX(sdes_repaired_rtp_stream_id, "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id") \
XX(video_timing, "http://www.webrtc.org/experiments/rtp-hdrext/video-timing") \
XX(color_space, "http://www.webrtc.org/experiments/rtp-hdrext/color-space") \
XX(csrc_audio_level, "urn:ietf:params:rtp-hdrext:csrc-audio-level") \
XX(framemarking, "http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07") \
XX(video_content_type, "http://www.webrtc.org/experiments/rtp-hdrext/video-content-type") \
XX(playout_delay, "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay") \
XX(video_orientation, "urn:3gpp:video-orientation") \
XX(toffset, "urn:ietf:params:rtp-hdrext:toffset") \
XX(encrypt, "urn:ietf:params:rtp-hdrext:encrypt")
#define XX(type, url) {RtpExtType::type , url},
static unordered_map<RtpExtType/*id*/, string/*ext*/> s_type_to_url = {RTP_EXT_MAP(XX)};
#undef XX
#define XX(type, url) {url, RtpExtType::type},
static unordered_map<string/*ext*/, RtpExtType/*id*/> s_url_to_type = {RTP_EXT_MAP(XX)};
#undef XX
RtpExtType RtpExt::getExtType(const string &url) {
auto it = s_url_to_type.find(url);
if (it == s_url_to_type.end()) {
throw std::invalid_argument(string("未识别的rtp ext url类型:") + url);
}
return it->second;
}
const string &RtpExt::getExtUrl(RtpExtType type) {
auto it = s_type_to_url.find(type);
if (it == s_type_to_url.end()) {
throw std::invalid_argument(string("未识别的rtp ext类型:") + to_string((int) type));
}
return it->second;
}
const char *RtpExt::getExtName(RtpExtType type) {
#define XX(type, url) case RtpExtType::type: return #type;
switch (type) {
RTP_EXT_MAP(XX)
default: return "unknown ext type";
}
#undef XX
}
string RtpExt::dumpString() const {
_StrPrinter printer;
switch (_type) {
case RtpExtType::ssrc_audio_level : {
bool vad;
printer << "audio level:" << (int) getAudioLevel(&vad) << ", vad:" << vad;
break;
}
case RtpExtType::abs_send_time : {
printer << "abs send time:" << getAbsSendTime();
break;
}
case RtpExtType::transport_cc : {
printer << "twcc seq:" << getTransportCCSeq();
break;
}
case RtpExtType::sdes_mid : {
printer << "sdes mid:" << getSdesMid();
break;
}
case RtpExtType::sdes_rtp_stream_id : {
printer << "rtp stream id:" << getRtpStreamId();
break;
}
case RtpExtType::sdes_repaired_rtp_stream_id : {
printer << "rtp repaired stream id:" << getRepairedRtpStreamId();
break;
}
case RtpExtType::video_timing : {
uint8_t flags;
uint16_t encode_start, encode_finish, packetization_complete, last_pkt_left_pacer, reserved_net0, reserved_net1;
getVideoTiming(flags, encode_start, encode_finish, packetization_complete, last_pkt_left_pacer,
reserved_net0, reserved_net1);
printer << "video timing, flags:" << (int) flags
<< ",encode:" << encode_start << "-" << encode_finish
<< ",packetization_complete:" << packetization_complete
<< ",last_pkt_left_pacer:" << last_pkt_left_pacer
<< ",reserved_net0:" << reserved_net0
<< ",reserved_net1:" << reserved_net1;
break;
}
case RtpExtType::video_content_type : {
printer << "video content type:" << (int)getVideoContentType();
break;
}
case RtpExtType::video_orientation : {
bool camera_bit, flip_bit, first_rotation, second_rotation;
getVideoOrientation(camera_bit, flip_bit, first_rotation, second_rotation);
printer << "video orientation:" << camera_bit << "-" << flip_bit << "-" << first_rotation << "-" << second_rotation;
break;
}
case RtpExtType::playout_delay : {
uint16_t min_delay, max_delay;
getPlayoutDelay(min_delay, max_delay);
printer << "playout delay:" << min_delay << "-" << max_delay;
break;
}
case RtpExtType::toffset : {
printer << "toffset:" << getTransmissionOffset();
break;
}
case RtpExtType::framemarking : {
printer << "framemarking tid:" << getFramemarkingTID();
break;
}
default: {
printer << getExtName(_type) << ", id:" << (int) _id << ", ";
printer << "hex:" << hexdump(data(), size());
break;
}
}
return std::move(printer);
}
//https://tools.ietf.org/html/rfc6464
// 0 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | len=0 |V| level |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Figure 1: Sample Audio Level Encoding Using the
// One-Byte Header Format
//
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | len=1 |V| level | 0 (pad) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Figure 2: Sample Audio Level Encoding Using the
// Two-Byte Header Format
uint8_t RtpExt::getAudioLevel(bool *vad) const{
CHECK(_type == RtpExtType::ssrc_audio_level && size() >= 1);
auto &byte = (*this)[0];
if (vad) {
*vad = byte & 0x80;
}
return byte & 0x7F;
}
//http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
//Wire format: 1-byte extension, 3 bytes of data. total 4 bytes extra per packet (plus shared 4 bytes for all extensions present: 2 byte magic word 0xBEDE, 2 byte # of extensions). Will in practice replace the “toffset” extension so we should see no long term increase in traffic as a result.
//
//Encoding: Timestamp is in seconds, 24 bit 6.18 fixed point, yielding 64s wraparound and 3.8us resolution (one increment for each 477 bytes going out on a 1Gbps interface).
//
//Relation to NTP timestamps: abs_send_time_24 = (ntp_timestamp_64 >> 14) & 0x00ffffff ; NTP timestamp is 32 bits for whole seconds, 32 bits fraction of second.
//
//Notes: Packets are time stamped when going out, preferably close to metal. Intermediate RTP relays (entities possibly altering the stream) should remove the extension or set its own timestamp.
uint32_t RtpExt::getAbsSendTime() const {
CHECK(_type == RtpExtType::abs_send_time && size() >= 3);
uint32_t ret = 0;
ret |= (*this)[0] << 16;
ret |= (*this)[1] << 8;
ret |= (*this)[2];
return ret;
}
//https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | 0xBE | 0xDE | length=1 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | L=1 |transport-wide sequence number | zero padding |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
uint16_t RtpExt::getTransportCCSeq() const {
CHECK(_type == RtpExtType::transport_cc && size() >= 2);
uint16_t ret;
ret |= (*this)[0] << 8;
ret |= (*this)[1];
return ret;
}
//https://tools.ietf.org/html/draft-ietf-avtext-sdes-hdr-ext-07
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | len | SDES Item text value ... |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
const string &RtpExt::getSdesMid() const {
CHECK(_type == RtpExtType::sdes_mid && size() >= 1);
return *this;
}
//https://tools.ietf.org/html/draft-ietf-avtext-rid-06
//用于simulecast
//3.1. RTCP 'RtpStreamId' SDES Extension
//
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |RtpStreamId=TBD| length | RtpStreamId ...
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
//
// The RtpStreamId payload is UTF-8 encoded and is not null-terminated.
//
// RFC EDITOR NOTE: Please replace TBD with the assigned SDES
// identifier value.
//3.2. RTCP 'RepairedRtpStreamId' SDES Extension
//
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |Repaired...=TBD| length | RepairRtpStreamId ...
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
//
// The RepairedRtpStreamId payload is UTF-8 encoded and is not null-
// terminated.
//
// RFC EDITOR NOTE: Please replace TBD with the assigned SDES
// identifier value.
string RtpExt::getRtpStreamId() const {
CHECK(_type == RtpExtType::sdes_rtp_stream_id && size() >= 1);
return *this;
}
string RtpExt::getRepairedRtpStreamId() const {
CHECK(_type == RtpExtType::sdes_repaired_rtp_stream_id && size() >= 1);
return *this;
}
//http://www.webrtc.org/experiments/rtp-hdrext/video-timing
//Wire format: 1-byte extension, 13 bytes of data. Total 14 bytes extra per packet (plus 1-3 padding byte in some cases, plus shared 4 bytes for all extensions present: 2 byte magic word 0xBEDE, 2 byte # of extensions).
//
//First byte is a flags field. Defined flags:
//
//0x01 - extension is set due to timer.
//0x02 - extension is set because the frame is larger than usual.
//Both flags may be set at the same time. All remaining 6 bits are reserved and should be ignored.
//
//Next, 6 timestamps are stored as 16-bit values in big-endian order, representing delta from the capture time of a packet in ms. Timestamps are, in order:
//
//Encode start.
//Encode finish.
//Packetization complete.
//Last packet left the pacer.
//Reserved for network.
//Reserved for network (2).
void RtpExt::getVideoTiming(uint8_t &flags,
uint16_t &encode_start,
uint16_t &encode_finish,
uint16_t &packetization_complete,
uint16_t &last_pkt_left_pacer,
uint16_t &reserved_net0,
uint16_t &reserved_net1) const {
CHECK(_type == RtpExtType::video_timing && size() >= 13);
flags = (*this)[0];
encode_start = (*this)[1] << 8 | (*this)[2];
encode_finish = (*this)[3] << 8 | (*this)[4];
packetization_complete = (*this)[5] << 8 | (*this)[6];
last_pkt_left_pacer = (*this)[7] << 8 | (*this)[8];
reserved_net0 = (*this)[9] << 8 | (*this)[10];
reserved_net1 = (*this)[11] << 8 | (*this)[12];
}
//http://www.webrtc.org/experiments/rtp-hdrext/color-space
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | L = 3 | primaries | transfer | matrix |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |range+chr.sit. |
// +-+-+-+-+-+-+-+-+
//http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
//Values:
//0x00: Unspecified. Default value. Treated the same as an absence of an extension.
//0x01: Screenshare. Video stream is of a screenshare type.
//0x02: 摄像头?
//Notes: Extension shoud be present only in the last packet of key-frames.
// If attached to other packets it should be ignored.
// If extension is absent, Unspecified value is assumed.
uint8_t RtpExt::getVideoContentType() const {
CHECK(_type == RtpExtType::video_content_type && size() >= 1);
return (*this)[0];
}
//http://www.3gpp.org/ftp/Specs/html-info/26114.htm
void RtpExt::getVideoOrientation(bool &camera_bit, bool &flip_bit, bool &first_rotation, bool &second_rotation) const {
CHECK(_type == RtpExtType::video_orientation && size() >= 1);
uint8_t byte = (*this)[0];
camera_bit = (byte & 0x08) >> 3;
flip_bit = (byte & 0x04) >> 2;
first_rotation = (byte & 0x02) >> 1;
second_rotation = byte & 0x01;
}
//http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
//+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//| ID | len=2 | MIN delay | MAX delay |
//+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
void RtpExt::getPlayoutDelay(uint16_t &min_delay, uint16_t &max_delay) const {
CHECK(_type == RtpExtType::playout_delay && size() >= 3);
uint32_t bytes = (*this)[0] << 16 | (*this)[1] << 8 | (*this)[2];
min_delay = (bytes & 0x00FFF000) >> 12;
max_delay = bytes & 0x00000FFF;
}
//urn:ietf:params:rtp-hdrext:toffset
//https://tools.ietf.org/html/rfc5450
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | len=2 | transmission offset |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
uint32_t RtpExt::getTransmissionOffset() const {
CHECK(_type == RtpExtType::toffset && size() >= 3);
return (*this)[0] << 16 | (*this)[1] << 8 | (*this)[2];
}
//http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID=? | L=2 |S|E|I|D|B| TID | LID | TL0PICIDX |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
uint8_t RtpExt::getFramemarkingTID() const {
CHECK(_type == RtpExtType::framemarking && size() >= 3);
return (*this)[0] & 0x07;
}