Add initial implementation of RTCP
Currently the timing of RTCP packets is not accurate and Sender/Receiver Reports contain non-canonical information. Support for SDES/APP/BYE packets is missing Support for actually using the RTCP information (ie. an API for the user) is missing
This commit is contained in:
parent
9970af92c9
commit
788a9f84cd
53
src/conn.cc
53
src/conn.cc
|
|
@ -24,6 +24,10 @@ kvz_rtp::connection::connection(bool reader):
|
|||
|
||||
kvz_rtp::connection::~connection()
|
||||
{
|
||||
if (rtcp_) {
|
||||
rtcp_->terminate();
|
||||
delete rtcp_;
|
||||
}
|
||||
}
|
||||
|
||||
void kvz_rtp::connection::set_payload(rtp_format_t fmt)
|
||||
|
|
@ -76,34 +80,59 @@ void kvz_rtp::connection::inc_rtp_sequence(size_t n)
|
|||
rtp_sequence_ += n;
|
||||
}
|
||||
|
||||
void kvz_rtp::connection::inc_rtp_sequence()
|
||||
{
|
||||
rtp_sequence_++;
|
||||
}
|
||||
|
||||
void kvz_rtp::connection::inc_processed_bytes(size_t n)
|
||||
{
|
||||
processed_bytes_ += n;
|
||||
rtcp_->sender_inc_processed_bytes(n);
|
||||
}
|
||||
|
||||
void kvz_rtp::connection::inc_overhead_bytes(size_t n)
|
||||
{
|
||||
overhead_bytes_ += n;
|
||||
rtcp_->sender_inc_overhead_bytes(n);
|
||||
}
|
||||
|
||||
void kvz_rtp::connection::inc_total_bytes(size_t n)
|
||||
{
|
||||
total_bytes_ += n;
|
||||
rtcp_->sender_inc_total_bytes(n);
|
||||
}
|
||||
|
||||
void kvz_rtp::connection::inc_processed_pkts(size_t n)
|
||||
{
|
||||
processed_pkts_ += n;
|
||||
rtcp_->sender_inc_processed_pkts(n);
|
||||
}
|
||||
|
||||
void kvz_rtp::connection::inc_processed_pkts()
|
||||
{
|
||||
processed_pkts_++;
|
||||
rtcp_->sender_inc_processed_pkts(1);
|
||||
}
|
||||
|
||||
void kvz_rtp::connection::inc_rtp_sequence()
|
||||
void kvz_rtp::connection::inc_processed_bytes(uint32_t ssrc, size_t n)
|
||||
{
|
||||
rtp_sequence_++;
|
||||
rtcp_->receiver_inc_processed_bytes(ssrc, n);
|
||||
}
|
||||
|
||||
void kvz_rtp::connection::inc_overhead_bytes(uint32_t ssrc, size_t n)
|
||||
{
|
||||
rtcp_->receiver_inc_overhead_bytes(ssrc, n);
|
||||
}
|
||||
|
||||
void kvz_rtp::connection::inc_total_bytes(uint32_t ssrc, size_t n)
|
||||
{
|
||||
rtcp_->receiver_inc_total_bytes(ssrc, n);
|
||||
}
|
||||
|
||||
void kvz_rtp::connection::inc_processed_pkts(uint32_t ssrc, size_t n)
|
||||
{
|
||||
rtcp_->receiver_inc_processed_pkts(ssrc, n);
|
||||
}
|
||||
|
||||
void kvz_rtp::connection::inc_processed_pkts(uint32_t ssrc)
|
||||
{
|
||||
rtcp_->receiver_inc_processed_pkts(ssrc, 1);
|
||||
}
|
||||
|
||||
void kvz_rtp::connection::fill_rtp_header(uint8_t *buffer, uint32_t timestamp)
|
||||
|
|
@ -118,3 +147,13 @@ void kvz_rtp::connection::fill_rtp_header(uint8_t *buffer, uint32_t timestamp)
|
|||
*(uint32_t *)&buffer[4] = htonl(timestamp);
|
||||
*(uint32_t *)&buffer[8] = htonl(rtp_ssrc_);
|
||||
}
|
||||
|
||||
rtp_error_t kvz_rtp::connection::create_rtcp(std::string dst_addr, int dst_port, int src_port)
|
||||
{
|
||||
if ((rtcp_ = new kvz_rtp::rtcp(dst_addr, dst_port, src_port, reader_)) == nullptr) {
|
||||
LOG_ERROR("Failed to allocate RTCP instance!");
|
||||
return RTP_MEMORY_ERROR;
|
||||
}
|
||||
|
||||
return rtcp_->start();
|
||||
}
|
||||
|
|
|
|||
32
src/conn.hh
32
src/conn.hh
|
|
@ -13,12 +13,12 @@
|
|||
#include <vector>
|
||||
|
||||
#include "frame.hh"
|
||||
#include "rtcp.hh"
|
||||
#include "socket.hh"
|
||||
#include "util.hh"
|
||||
|
||||
namespace kvz_rtp {
|
||||
class connection {
|
||||
|
||||
public:
|
||||
connection(bool reader);
|
||||
virtual ~connection();
|
||||
|
|
@ -35,18 +35,26 @@ namespace kvz_rtp {
|
|||
void set_payload(rtp_format_t fmt);
|
||||
void set_ssrc(uint32_t ssrc);
|
||||
|
||||
/* functions for increasing various RTP statistics
|
||||
/* Functions for increasing various RTP statistics
|
||||
* Overloaded functions without parameters increase the counter by 1
|
||||
*
|
||||
* overloaded functions without parameters increase the counter by 1*/
|
||||
* Functions that take SSRC are for updating receiver statistics
|
||||
*
|
||||
* TODO: jitter! */
|
||||
void inc_rtp_sequence(size_t n);
|
||||
void inc_processed_bytes(size_t n);
|
||||
void inc_overhead_bytes(size_t n);
|
||||
void inc_total_bytes(size_t n);
|
||||
void inc_processed_pkts(size_t n);
|
||||
|
||||
void inc_processed_pkts();
|
||||
void inc_rtp_sequence();
|
||||
|
||||
void inc_processed_bytes(uint32_t ssrc, size_t n);
|
||||
void inc_overhead_bytes(uint32_t ssrc, size_t n);
|
||||
void inc_total_bytes(uint32_t ssrc, size_t n);
|
||||
void inc_processed_pkts(uint32_t ssrc, size_t n);
|
||||
void inc_processed_pkts(uint32_t ssrc);
|
||||
|
||||
/* config set and get */
|
||||
void set_config(void *config);
|
||||
void *get_config();
|
||||
|
|
@ -55,11 +63,20 @@ namespace kvz_rtp {
|
|||
* caller must make sure that the buffer is at least 12 bytes long */
|
||||
void fill_rtp_header(uint8_t *buffer, uint32_t timestamp);
|
||||
|
||||
/* Create RTCP instance for this connection
|
||||
*
|
||||
* This instance listens to src_port for incoming RTCP reports and sends
|
||||
* repots about this session to dst_addr:dst_port every N seconds (see RFC 3550) */
|
||||
rtp_error_t create_rtcp(std::string dst_addr, int dst_port, int src_port);
|
||||
|
||||
rtp_error_t add_rtcp_participant(kvz_rtp::connection *conn);
|
||||
|
||||
protected:
|
||||
void *config_;
|
||||
uint32_t id_;
|
||||
|
||||
kvz_rtp::socket socket_;
|
||||
kvz_rtp::rtcp *rtcp_;
|
||||
|
||||
private:
|
||||
bool reader_;
|
||||
|
|
@ -68,12 +85,5 @@ namespace kvz_rtp {
|
|||
uint16_t rtp_sequence_;
|
||||
uint8_t rtp_payload_;
|
||||
uint32_t rtp_ssrc_;
|
||||
|
||||
/* statistics for RTCP reports */
|
||||
size_t processed_bytes_;
|
||||
size_t overhead_bytes_;
|
||||
size_t total_bytes_;
|
||||
size_t processed_pkts_;
|
||||
size_t dropped_pkts_;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -57,9 +57,9 @@ namespace kvz_rtp {
|
|||
PACKED_STRUCT(rtcp_sender_info) {
|
||||
uint32_t ntp_msw;
|
||||
uint32_t ntp_lsw;
|
||||
uint32_t rtp_timestamp;
|
||||
uint32_t pkt_count;
|
||||
uint32_t byte_count;
|
||||
uint32_t rtp_ts;
|
||||
uint32_t pkt_cnt;
|
||||
uint32_t byte_cnt;
|
||||
};
|
||||
|
||||
PACKED_STRUCT(rtcp_report_block) {
|
||||
|
|
|
|||
|
|
@ -105,7 +105,6 @@ rtp_error_t kvz_rtp::context::destroy_writer(kvz_rtp::writer *writer)
|
|||
{
|
||||
conns_.erase(writer->get_ssrc());
|
||||
|
||||
/* TODO: rtcp bye */
|
||||
delete writer;
|
||||
return RTP_OK;
|
||||
}
|
||||
|
|
@ -114,7 +113,6 @@ rtp_error_t kvz_rtp::context::destroy_reader(kvz_rtp::reader *reader)
|
|||
{
|
||||
conns_.erase(reader->get_ssrc());
|
||||
|
||||
/* TODO: rtcp bye */
|
||||
delete reader;
|
||||
return RTP_OK;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -166,6 +166,11 @@ void kvz_rtp::reader::frame_receiver(kvz_rtp::reader *reader)
|
|||
frame->payload_len = nread - kvz_rtp::frame::HEADER_SIZE_RTP;
|
||||
frame->total_len = nread;
|
||||
|
||||
reader->inc_processed_bytes(frame->ssrc, frame->total_len);
|
||||
reader->inc_overhead_bytes(frame->ssrc, kvz_rtp::frame::HEADER_SIZE_RTP); /* TODO: this is not accurate! */
|
||||
reader->inc_total_bytes(frame->ssrc, frame->total_len);
|
||||
reader->inc_processed_pkts(frame->ssrc, 1);
|
||||
|
||||
if (!frame->data) {
|
||||
LOG_ERROR("Failed to allocate buffer for RTP frame!");
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,421 @@
|
|||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "debug.hh"
|
||||
#include "rtcp.hh"
|
||||
#include "util.hh"
|
||||
|
||||
kvz_rtp::rtcp::rtcp():
|
||||
tp_(0), tc_(0), tn_(0), pmembers_(0),
|
||||
members_(0), senders_(0), rtcp_bandwidth_(0),
|
||||
we_sent_(0), avg_rtcp_pkt_pize_(0), initial_(true),
|
||||
active_(false), send_addr_(""), send_port_(0), recv_port_(0),
|
||||
socket_(), num_receivers_(0)
|
||||
{
|
||||
cname_ = "hello"; //generate_cname();
|
||||
}
|
||||
|
||||
kvz_rtp::rtcp::rtcp(std::string dst_addr, int dst_port, bool receiver):
|
||||
rtcp()
|
||||
{
|
||||
send_addr_ = dst_addr;
|
||||
send_port_ = dst_port;
|
||||
receiver_ = receiver;
|
||||
}
|
||||
|
||||
kvz_rtp::rtcp::rtcp(std::string dst_addr, int dst_port, int src_port, bool receiver):
|
||||
rtcp(dst_addr, dst_port, receiver)
|
||||
{
|
||||
recv_port_ = src_port;
|
||||
}
|
||||
|
||||
kvz_rtp::rtcp::~rtcp()
|
||||
{
|
||||
}
|
||||
|
||||
rtp_error_t kvz_rtp::rtcp::start()
|
||||
{
|
||||
if (send_addr_ == "" || send_port_ == 0) {
|
||||
LOG_ERROR("Invalid values given (%s, %d), cannot create RTCP instance", send_addr_.c_str(), send_port_);
|
||||
return RTP_INVALID_VALUE;
|
||||
}
|
||||
|
||||
rtp_error_t ret;
|
||||
|
||||
if ((ret = socket_.init(AF_INET, SOCK_DGRAM, 0)) != RTP_OK)
|
||||
return ret;
|
||||
|
||||
/* if the receive port was given, this RTCP instance
|
||||
* is expecting status reports to that port and should bind the socket to it */
|
||||
if (recv_port_ != 0) {
|
||||
int enable = 1;
|
||||
|
||||
if ((ret = socket_.setsockopt(SOL_SOCKET, SO_REUSEADDR, (const char *)&enable, sizeof(int))) != RTP_OK)
|
||||
return ret;
|
||||
|
||||
/* Set read timeout (5s for now)
|
||||
*
|
||||
* TODO: this doesn't btw work...
|
||||
*
|
||||
* This means that the socket is listened for 5s at a time and after the timeout,
|
||||
* Send Report is sent to all participants */
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 3;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
if ((ret = socket_.setsockopt(SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) != RTP_OK)
|
||||
return ret;
|
||||
|
||||
LOG_DEBUG("Binding to port %d (source port)", recv_port_);
|
||||
|
||||
if ((ret = socket_.bind(AF_INET, INADDR_ANY, recv_port_)) != RTP_OK)
|
||||
return ret;
|
||||
}
|
||||
|
||||
socket_.set_sockaddr(socket_.create_sockaddr(AF_INET, send_addr_, send_port_));
|
||||
|
||||
active_ = true;
|
||||
|
||||
if ((runner_ = new std::thread(rtcp_runner, this)) == nullptr) {
|
||||
active_ = false;
|
||||
LOG_ERROR("Failed to create RTCP thread!");
|
||||
return RTP_MEMORY_ERROR;
|
||||
}
|
||||
runner_->detach();
|
||||
|
||||
return RTP_OK;
|
||||
}
|
||||
|
||||
rtp_error_t kvz_rtp::rtcp::terminate()
|
||||
{
|
||||
/* when the member count is less than 50,
|
||||
* we can just send the BYE message and destroy the session */
|
||||
if (members_ < 50) {
|
||||
active_ = false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
tp_ = tc_;
|
||||
members_ = 1;
|
||||
pmembers_ = 1;
|
||||
initial_ = true;
|
||||
we_sent_ = false;
|
||||
senders_ = 0;
|
||||
active_ = false;
|
||||
|
||||
end:
|
||||
/* free all receiver statistic structs */
|
||||
for (auto& i : receiver_stats_) {
|
||||
delete i.second;
|
||||
}
|
||||
|
||||
return RTP_OK;
|
||||
}
|
||||
|
||||
bool kvz_rtp::rtcp::active() const
|
||||
{
|
||||
return active_;
|
||||
}
|
||||
|
||||
bool kvz_rtp::rtcp::receiver() const
|
||||
{
|
||||
return receiver_;
|
||||
}
|
||||
|
||||
kvz_rtp::socket& kvz_rtp::rtcp::get_socket()
|
||||
{
|
||||
return socket_;
|
||||
}
|
||||
|
||||
void kvz_rtp::rtcp::sender_inc_processed_bytes(size_t n)
|
||||
{
|
||||
sender_stats_.processed_bytes += n;
|
||||
}
|
||||
|
||||
void kvz_rtp::rtcp::sender_inc_overhead_bytes(size_t n)
|
||||
{
|
||||
sender_stats_.overhead_bytes += n;
|
||||
}
|
||||
|
||||
void kvz_rtp::rtcp::sender_inc_total_bytes(size_t n)
|
||||
{
|
||||
sender_stats_.total_bytes += n;
|
||||
}
|
||||
|
||||
void kvz_rtp::rtcp::sender_inc_processed_pkts(size_t n)
|
||||
{
|
||||
sender_stats_.processed_pkts += n;
|
||||
}
|
||||
|
||||
void kvz_rtp::rtcp::check_sender(uint32_t ssrc)
|
||||
{
|
||||
if (receiver_stats_.find(ssrc) == receiver_stats_.end()) {
|
||||
LOG_INFO("First RTP packet from 0x%x", ssrc);
|
||||
|
||||
receiver_stats_[ssrc] = new struct statistics;
|
||||
num_receivers_++;
|
||||
}
|
||||
}
|
||||
|
||||
void kvz_rtp::rtcp::receiver_inc_processed_bytes(uint32_t sender_ssrc, size_t n)
|
||||
{
|
||||
check_sender(sender_ssrc);
|
||||
|
||||
receiver_stats_[sender_ssrc]->processed_bytes += n;
|
||||
}
|
||||
|
||||
void kvz_rtp::rtcp::receiver_inc_overhead_bytes(uint32_t sender_ssrc, size_t n)
|
||||
{
|
||||
check_sender(sender_ssrc);
|
||||
|
||||
receiver_stats_[sender_ssrc]->overhead_bytes += n;
|
||||
}
|
||||
|
||||
void kvz_rtp::rtcp::receiver_inc_total_bytes(uint32_t sender_ssrc, size_t n)
|
||||
{
|
||||
check_sender(sender_ssrc);
|
||||
|
||||
receiver_stats_[sender_ssrc]->total_bytes += n;
|
||||
}
|
||||
|
||||
void kvz_rtp::rtcp::receiver_inc_processed_pkts(uint32_t sender_ssrc, size_t n)
|
||||
{
|
||||
check_sender(sender_ssrc);
|
||||
|
||||
receiver_stats_[sender_ssrc]->processed_pkts += n;
|
||||
}
|
||||
|
||||
rtp_error_t kvz_rtp::rtcp::generate_sender_report()
|
||||
{
|
||||
return RTP_OK;
|
||||
|
||||
LOG_INFO("Generating sender report...");
|
||||
|
||||
kvz_rtp::frame::rtcp_sender_frame *frame;
|
||||
|
||||
if ((frame = kvz_rtp::frame::alloc_rtcp_sender_frame(num_receivers_)) == nullptr) {
|
||||
LOG_ERROR("Failed to allocate RTCP Receiver Report frame!");
|
||||
return rtp_errno;
|
||||
}
|
||||
|
||||
size_t ptr = 0;
|
||||
rtp_error_t ret = RTP_OK;
|
||||
uint64_t timestamp = tv_to_ntp();
|
||||
|
||||
frame->s_info.ntp_msw = timestamp >> 32;
|
||||
frame->s_info.ntp_lsw = timestamp & 0xffffffff;
|
||||
frame->s_info.rtp_ts = 3; /* TODO: what timestamp is this? */
|
||||
frame->s_info.pkt_cnt = sender_stats_.processed_pkts;
|
||||
frame->s_info.byte_cnt = sender_stats_.processed_bytes;
|
||||
|
||||
for (auto& receiver : receiver_stats_) {
|
||||
frame->blocks[ptr].ssrc = receiver.first;
|
||||
|
||||
if (receiver.second->dropped_pkts) {
|
||||
frame->blocks[ptr].fraction_lost =
|
||||
receiver.second->processed_pkts / receiver.second->dropped_pkts;
|
||||
}
|
||||
|
||||
frame->blocks[ptr].cumulative_pkt_lost = receiver.second->dropped_pkts;
|
||||
frame->blocks[ptr].highest_seq_recved = 222; // TODO ???
|
||||
frame->blocks[ptr].interraival_jitter = 333; // TODO ???
|
||||
frame->blocks[ptr].delay_since_last_sr = 555; // TODO ???
|
||||
frame->blocks[ptr].last_sr = 444; // TODO ???
|
||||
|
||||
ptr++;
|
||||
}
|
||||
|
||||
ret = socket_.sendto((uint8_t *)frame, frame->header.length, 0, NULL);
|
||||
kvz_rtp::frame::dealloc_frame(frame);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
rtp_error_t kvz_rtp::rtcp::generate_receiver_report()
|
||||
{
|
||||
LOG_INFO("Generating receiver report...");
|
||||
|
||||
kvz_rtp::frame::rtcp_receiver_frame *frame;
|
||||
|
||||
if ((frame = kvz_rtp::frame::alloc_rtcp_receiver_frame(num_receivers_)) == nullptr) {
|
||||
LOG_ERROR("Failed to allocate RTCP Receiver Report frame!");
|
||||
return rtp_errno;
|
||||
}
|
||||
|
||||
rtp_error_t ret = RTP_OK;
|
||||
size_t ptr = 0;
|
||||
|
||||
for (auto& receiver : receiver_stats_) {
|
||||
frame->blocks[ptr].ssrc = receiver.first;
|
||||
|
||||
if (receiver.second->dropped_pkts) {
|
||||
frame->blocks[ptr].fraction_lost =
|
||||
receiver.second->processed_pkts / receiver.second->dropped_pkts;
|
||||
}
|
||||
|
||||
frame->blocks[ptr].cumulative_pkt_lost = receiver.second->dropped_pkts;
|
||||
frame->blocks[ptr].highest_seq_recved = 222; // TODO ???
|
||||
frame->blocks[ptr].interraival_jitter = 333; // TODO ???
|
||||
frame->blocks[ptr].delay_since_last_sr = 555; // TODO ???
|
||||
frame->blocks[ptr].last_sr = 444; // TODO ???
|
||||
|
||||
ptr++;
|
||||
}
|
||||
|
||||
ret = socket_.sendto((uint8_t *)frame, frame->header.length, 0, NULL);
|
||||
kvz_rtp::frame::dealloc_frame(frame);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
rtp_error_t kvz_rtp::rtcp::generate_report()
|
||||
{
|
||||
if (receiver_)
|
||||
return generate_receiver_report();
|
||||
return generate_sender_report();
|
||||
}
|
||||
|
||||
rtp_error_t kvz_rtp::rtcp::handle_sender_packet(kvz_rtp::frame::rtcp_sender_frame *report)
|
||||
{
|
||||
if (!report)
|
||||
return RTP_INVALID_VALUE;
|
||||
|
||||
if (report->header.report_cnt == 0) {
|
||||
LOG_ERROR("Receiver report cannot have 0 report blocks!");
|
||||
return RTP_INVALID_VALUE;
|
||||
}
|
||||
|
||||
/* TODO: what are we supposed to with this report? */
|
||||
|
||||
return RTP_OK;
|
||||
}
|
||||
|
||||
rtp_error_t kvz_rtp::rtcp::handle_receiver_packet(kvz_rtp::frame::rtcp_receiver_frame *report)
|
||||
{
|
||||
if (!report)
|
||||
return RTP_INVALID_VALUE;
|
||||
|
||||
if (report->header.report_cnt == 0) {
|
||||
LOG_ERROR("Receiver report cannot have 0 report blocks!");
|
||||
return RTP_INVALID_VALUE;
|
||||
}
|
||||
|
||||
/* TODO: what are we supposed to with this report? */
|
||||
|
||||
return RTP_OK;
|
||||
}
|
||||
|
||||
rtp_error_t kvz_rtp::rtcp::handle_sdes_packet(kvz_rtp::frame::rtcp_sdes_frame *sdes)
|
||||
{
|
||||
(void)sdes;
|
||||
return RTP_OK;
|
||||
}
|
||||
|
||||
rtp_error_t kvz_rtp::rtcp::handle_bye_packet(kvz_rtp::frame::rtcp_bye_frame *bye)
|
||||
{
|
||||
(void)bye;
|
||||
return RTP_OK;
|
||||
}
|
||||
|
||||
rtp_error_t kvz_rtp::rtcp::handle_app_packet(kvz_rtp::frame::rtcp_app_frame *app)
|
||||
{
|
||||
(void)app;
|
||||
return RTP_OK;
|
||||
}
|
||||
|
||||
rtp_error_t kvz_rtp::rtcp::handle_incoming_packet(uint8_t *buffer, size_t size)
|
||||
{
|
||||
kvz_rtp::frame::rtcp_header *header = (kvz_rtp::frame::rtcp_header *)buffer;
|
||||
|
||||
if (header->version != 2) {
|
||||
LOG_ERROR("Invalid header version (%u)", header->version);
|
||||
return RTP_INVALID_VALUE;
|
||||
}
|
||||
|
||||
if (header->padding != 0) {
|
||||
LOG_ERROR("Cannot handle padded packets!");
|
||||
return RTP_INVALID_VALUE;
|
||||
}
|
||||
|
||||
if (header->pkt_type > kvz_rtp::frame::FRAME_TYPE_BYE ||
|
||||
header->pkt_type < kvz_rtp::frame::FRAME_TYPE_SR) {
|
||||
LOG_ERROR("Invalid packet type (%u)!", header->pkt_type);
|
||||
return RTP_INVALID_VALUE;
|
||||
}
|
||||
|
||||
rtp_error_t ret;
|
||||
|
||||
switch (header->pkt_type) {
|
||||
case kvz_rtp::frame::FRAME_TYPE_SR:
|
||||
ret = handle_sender_packet((kvz_rtp::frame::rtcp_sender_frame *)buffer);
|
||||
break;
|
||||
|
||||
case kvz_rtp::frame::FRAME_TYPE_RR:
|
||||
ret = handle_receiver_packet((kvz_rtp::frame::rtcp_receiver_frame *)buffer);
|
||||
break;
|
||||
|
||||
case kvz_rtp::frame::FRAME_TYPE_SDES:
|
||||
/* ret = handle_sender_packet((kvz_rtp::frame::rtcp_sender_frame *)buffer); */
|
||||
break;
|
||||
|
||||
case kvz_rtp::frame::FRAME_TYPE_BYE:
|
||||
/* ret = handle_sender_packet((kvz_rtp::frame::rtcp_sender_frame *)buffer); */
|
||||
break;
|
||||
|
||||
case kvz_rtp::frame::FRAME_TYPE_APP:
|
||||
/* ret = handle_app_packet((kvz_rtp::frame::rtcp_sender_frame *)buffer); */
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void kvz_rtp::rtcp::rtcp_runner(kvz_rtp::rtcp *rtcp)
|
||||
{
|
||||
LOG_INFO("RTCP instance created!");
|
||||
|
||||
int nread;
|
||||
uint8_t buffer[MAX_PACKET];
|
||||
|
||||
kvz_rtp::socket socket = rtcp->get_socket();
|
||||
rtp_error_t ret;
|
||||
|
||||
while (rtcp->active()) {
|
||||
ret = socket.recvfrom(buffer, MAX_PACKET, 0, &nread);
|
||||
|
||||
if (ret == RTP_OK && nread > 0) {
|
||||
(void)rtcp->handle_incoming_packet(buffer, (size_t)nread);
|
||||
} else if (ret == RTP_INTERRUPTED) {
|
||||
/* do nothing */
|
||||
} else {
|
||||
LOG_ERROR("recvfrom failed, %d", ret);
|
||||
}
|
||||
|
||||
if ((ret = rtcp->generate_report()) != RTP_OK) {
|
||||
LOG_ERROR("Failed to send RTCP status report!");
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: send bye */
|
||||
}
|
||||
|
||||
uint64_t kvz_rtp::rtcp::tv_to_ntp()
|
||||
{
|
||||
static const uint64_t EPOCH = 2208988800ULL;
|
||||
static const uint64_t NTP_SCALE_FRAC = 4294967296ULL;
|
||||
|
||||
static struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
|
||||
uint64_t tv_ntp, tv_usecs;
|
||||
|
||||
tv_ntp = tv.tv_sec + EPOCH;
|
||||
tv_usecs = (NTP_SCALE_FRAC * tv.tv_usec) / 1000000UL;
|
||||
|
||||
return (tv_ntp << 32) | tv_usecs;
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "frame.hh"
|
||||
#include "socket.hh"
|
||||
#include "util.hh"
|
||||
|
||||
namespace kvz_rtp {
|
||||
|
||||
class connection;
|
||||
|
||||
class rtcp {
|
||||
public:
|
||||
rtcp();
|
||||
rtcp(std::string dst_addr, int dst_port, bool receiver);
|
||||
rtcp(std::string dst_addr, int dst_port, int src_port, bool receiver);
|
||||
~rtcp();
|
||||
|
||||
/* start the RTCP runner thread
|
||||
*
|
||||
* return RTP_OK on success and RTP_MEMORY_ERROR if the allocation fails */
|
||||
rtp_error_t start();
|
||||
|
||||
/* End the RTCP session and send RTCP BYE to all participants
|
||||
*
|
||||
* return RTP_OK on success */
|
||||
rtp_error_t terminate();
|
||||
|
||||
/* return true if the connection is still considered active
|
||||
* and RTCP transmissions should continue */
|
||||
bool active() const;
|
||||
|
||||
/* return true if this RTCP instance belongs to an RTP receiver
|
||||
* and a receiver report should be generated, otherwise sender report is generated */
|
||||
bool receiver() const;
|
||||
|
||||
/* Generate either RTCP Sender or Receiver report and sent it to all participants
|
||||
* Return RTP_OK on success and RTP_ERROR on error */
|
||||
rtp_error_t generate_report();
|
||||
|
||||
/* Handle different kinds of incoming packets
|
||||
*
|
||||
* Currently nothing's done with valid packets, at some point an API for
|
||||
* querying these repots is implemented
|
||||
*
|
||||
* Return RTP_OK on success and RTP_ERROR on error */
|
||||
rtp_error_t handle_sender_packet(kvz_rtp::frame::rtcp_sender_frame *sender_report);
|
||||
rtp_error_t handle_receiver_packet(kvz_rtp::frame::rtcp_receiver_frame *receiver_report);
|
||||
rtp_error_t handle_sdes_packet(kvz_rtp::frame::rtcp_sdes_frame *sdes);
|
||||
rtp_error_t handle_bye_packet(kvz_rtp::frame::rtcp_bye_frame *bye);
|
||||
rtp_error_t handle_app_packet(kvz_rtp::frame::rtcp_app_frame *app);
|
||||
|
||||
/* Handle incoming RTCP packet (first make sure it's a valid RTCP packet)
|
||||
* This function will call one of the above functions internally
|
||||
*
|
||||
* Return RTP_OK on success and RTP_ERROR on error */
|
||||
rtp_error_t handle_incoming_packet(uint8_t *buffer, size_t size);
|
||||
|
||||
/* Return reference to kvz_rtp::socket of the RTCP instance
|
||||
* Used by the rtcp_runner to listen to incoming */
|
||||
kvz_rtp::socket& get_socket();
|
||||
|
||||
/* Somebody joined the multicast group the owner of this RTCP instance is part of
|
||||
* Add it to RTCP participant list so we can start listening for reports
|
||||
*
|
||||
* Return RTP_OK on success and RTP_ERROR on error */
|
||||
rtp_error_t add_participant(kvz_rtp::connection *conn);
|
||||
|
||||
/* Functions for updating various RTP sender statistics */
|
||||
void sender_inc_processed_bytes(size_t n);
|
||||
void sender_inc_overhead_bytes(size_t n);
|
||||
void sender_inc_total_bytes(size_t n);
|
||||
void sender_inc_processed_pkts(size_t n);
|
||||
|
||||
void receiver_inc_processed_bytes(uint32_t sender_ssrc, size_t n);
|
||||
void receiver_inc_overhead_bytes(uint32_t sender_ssrc, size_t n);
|
||||
void receiver_inc_total_bytes(uint32_t sender_ssrc, size_t n);
|
||||
void receiver_inc_processed_pkts(uint32_t sender_ssrc, size_t n);
|
||||
|
||||
private:
|
||||
static void rtcp_runner(rtcp *rtcp);
|
||||
|
||||
/* convert struct timeval format to NTP format */
|
||||
uint64_t tv_to_ntp();
|
||||
|
||||
/* when we start the RTCP instance, we don't know what the SSRC of the remote is
|
||||
* when an RTP packet is received, we must check if we've already received a packet
|
||||
* from this sender and if not, create new entry to receiver_stats_ map */
|
||||
void check_sender(uint32_t ssrc);
|
||||
|
||||
/* Functions for generating different kinds of reports.
|
||||
* These functions will both generate the report and send it
|
||||
*
|
||||
* Return RTP_OK on success and RTP_ERROR on error */
|
||||
rtp_error_t generate_sender_report();
|
||||
rtp_error_t generate_receiver_report();
|
||||
|
||||
std::thread *runner_;
|
||||
std::string cname_;
|
||||
bool receiver_;
|
||||
|
||||
/* TODO: time_t?? */
|
||||
size_t tp_; /* the last time an RTCP packet was transmitted */
|
||||
size_t tc_; /* the current time */
|
||||
size_t tn_; /* the next scheduled transmission time of an RTCP packet */
|
||||
size_t pmembers_; /* the estimated number of session members at the time tn was last recomputed */
|
||||
size_t members_; /* the most current estimate for the number of session members */
|
||||
size_t senders_; /* the most current estimate for the number of senders in the session */
|
||||
|
||||
/* The target RTCP bandwidth, i.e., the total bandwidth
|
||||
* that will be used for RTCP packets by all members of this session,
|
||||
* in octets per second. This will be a specified fraction of the
|
||||
* "session bandwidth" parameter supplied to the application at startup. */
|
||||
size_t rtcp_bandwidth_;
|
||||
|
||||
/* Flag that is true if the application has sent data since
|
||||
* the 2nd previous RTCP report was transmitted. */
|
||||
bool we_sent_;
|
||||
|
||||
/* The average compound RTCP packet size, in octets,
|
||||
* over all RTCP packets sent and received by this participant. The
|
||||
* size includes lower-layer transport and network protocol headers
|
||||
* (e.g., UDP and IP) as explained in Section 6.2 */
|
||||
size_t avg_rtcp_pkt_pize_;
|
||||
|
||||
/* Flag that is true if the application has not yet sent an RTCP packet. */
|
||||
bool initial_;
|
||||
|
||||
/* Flag that is true if the connection is still considered open.
|
||||
* When clients decided to leave, he calls rtcp->terminate()
|
||||
* which stops the rtcp runner and RTCP BYE message to all participants */
|
||||
bool active_;
|
||||
|
||||
/* send_addr_:send_port_ is used for sending reports to remote
|
||||
* recv_port_ is used for receiving reports */
|
||||
std::string send_addr_;
|
||||
int send_port_;
|
||||
int recv_port_;
|
||||
|
||||
kvz_rtp::socket socket_;
|
||||
|
||||
/* TODO: is there any use for this?? */
|
||||
std::vector<kvz_rtp::connection *> participant_;
|
||||
|
||||
struct statistics {
|
||||
size_t processed_bytes;
|
||||
size_t overhead_bytes;
|
||||
size_t total_bytes;
|
||||
size_t processed_pkts;
|
||||
size_t dropped_pkts;
|
||||
};
|
||||
|
||||
/* statistics for RTCP Sender and Receiver Reports */
|
||||
struct statistics sender_stats_;
|
||||
std::map<uint32_t, struct statistics *> receiver_stats_;
|
||||
size_t num_receivers_;
|
||||
};
|
||||
};
|
||||
|
|
@ -139,6 +139,9 @@ rtp_error_t kvz_rtp::socket::recvfrom(uint8_t *buf, size_t buf_len, int flags, i
|
|||
::recvfrom(socket_, buf, buf_len, flags, (struct sockaddr *)&from_addr, (socklen_t *)&from_addr_size);
|
||||
|
||||
if (ret == -1) {
|
||||
if (errno == EAGAIN)
|
||||
return RTP_INTERRUPTED;
|
||||
|
||||
LOG_ERROR("recvfrom failed: %s", strerror(errno));
|
||||
return RTP_GENERIC_ERROR;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue