rtpdec: Support sending RTCP feedback packets

This sends NACK for missed packets and PLI (picture loss indication)
if a depacketizer indicates that it needs a new keyframe, according
to RFC 4585.

This is only enabled if the SDP indicated that feedback is supported
(via the AVPF or SAVPF profile names).

The feedback packets are throttled to a certain maximum interval
(currently 250 ms) to make sure the feedback packets don't eat up
too much bandwidth (which might be counterproductive). The RFC
specifies a more elaborate feedback packet scheduling.

The feedback packets are currently sent independently from normal
RTCP RR packets, which is not totally spec compliant, but works
fine in the environments I've tested it in. (RFC 5506 allows this,
but requires a SDP attribute for enabling it.)

Signed-off-by: Martin Storsjö <martin@martin.st>
This commit is contained in:
Martin Storsjö 2012-12-11 15:59:24 +02:00
parent 42805eda55
commit 86d9181cf4
5 changed files with 113 additions and 1 deletions

View File

@ -30,6 +30,8 @@
#include "rtpdec.h" #include "rtpdec.h"
#include "rtpdec_formats.h" #include "rtpdec_formats.h"
#define MIN_FEEDBACK_INTERVAL 200000 /* 200 ms in us */
static RTPDynamicProtocolHandler realmedia_mp3_dynamic_handler = { static RTPDynamicProtocolHandler realmedia_mp3_dynamic_handler = {
.enc_name = "X-MP3-draft-00", .enc_name = "X-MP3-draft-00",
.codec_type = AVMEDIA_TYPE_AUDIO, .codec_type = AVMEDIA_TYPE_AUDIO,
@ -366,6 +368,100 @@ void ff_rtp_send_punch_packets(URLContext *rtp_handle)
av_free(buf); av_free(buf);
} }
static int find_missing_packets(RTPDemuxContext *s, uint16_t *first_missing,
uint16_t *missing_mask)
{
int i;
uint16_t next_seq = s->seq + 1;
RTPPacket *pkt = s->queue;
if (!pkt || pkt->seq == next_seq)
return 0;
*missing_mask = 0;
for (i = 1; i <= 16; i++) {
uint16_t missing_seq = next_seq + i;
while (pkt) {
int16_t diff = pkt->seq - missing_seq;
if (diff >= 0)
break;
pkt = pkt->next;
}
if (!pkt)
break;
if (pkt->seq == missing_seq)
continue;
*missing_mask |= 1 << (i - 1);
}
*first_missing = next_seq;
return 1;
}
int ff_rtp_send_rtcp_feedback(RTPDemuxContext *s, URLContext *fd,
AVIOContext *avio)
{
int len, need_keyframe, missing_packets;
AVIOContext *pb;
uint8_t *buf;
int64_t now;
uint16_t first_missing, missing_mask;
if (!fd && !avio)
return -1;
need_keyframe = s->handler && s->handler->need_keyframe &&
s->handler->need_keyframe(s->dynamic_protocol_context);
missing_packets = find_missing_packets(s, &first_missing, &missing_mask);
if (!need_keyframe && !missing_packets)
return 0;
/* Send new feedback if enough time has elapsed since the last
* feedback packet. */
now = av_gettime();
if (s->last_feedback_time &&
(now - s->last_feedback_time) < MIN_FEEDBACK_INTERVAL)
return 0;
s->last_feedback_time = now;
if (!fd)
pb = avio;
else if (avio_open_dyn_buf(&pb) < 0)
return -1;
if (need_keyframe) {
avio_w8(pb, (RTP_VERSION << 6) | 1); /* PLI */
avio_w8(pb, RTCP_PSFB);
avio_wb16(pb, 2); /* length in words - 1 */
// our own SSRC: we use the server's SSRC + 1 to avoid conflicts
avio_wb32(pb, s->ssrc + 1);
avio_wb32(pb, s->ssrc); // server SSRC
}
if (missing_packets) {
avio_w8(pb, (RTP_VERSION << 6) | 1); /* NACK */
avio_w8(pb, RTCP_RTPFB);
avio_wb16(pb, 3); /* length in words - 1 */
avio_wb32(pb, s->ssrc + 1);
avio_wb32(pb, s->ssrc); // server SSRC
avio_wb16(pb, first_missing);
avio_wb16(pb, missing_mask);
}
avio_flush(pb);
if (!fd)
return 0;
len = avio_close_dyn_buf(pb, &buf);
if (len > 0 && buf) {
ffurl_write(fd, buf, len);
av_free(buf);
}
return 0;
}
/** /**
* open a new RTP parse context for stream 'st'. 'st' can be NULL for * open a new RTP parse context for stream 'st'. 'st' can be NULL for
* MPEG2-TS streams to indicate that they should be demuxed inside the * MPEG2-TS streams to indicate that they should be demuxed inside the

View File

@ -73,6 +73,8 @@ void ff_rtp_send_punch_packets(URLContext* rtp_handle);
*/ */
int ff_rtp_check_and_send_back_rr(RTPDemuxContext *s, URLContext *fd, int ff_rtp_check_and_send_back_rr(RTPDemuxContext *s, URLContext *fd,
AVIOContext *avio, int count); AVIOContext *avio, int count);
int ff_rtp_send_rtcp_feedback(RTPDemuxContext *s, URLContext *fd,
AVIOContext *avio);
// these statistics are used for rtcp receiver reports... // these statistics are used for rtcp receiver reports...
typedef struct RTPStatistics { typedef struct RTPStatistics {
@ -130,6 +132,7 @@ struct RTPDynamicProtocolHandler {
void (*free)(PayloadContext *protocol_data); void (*free)(PayloadContext *protocol_data);
/** Parse handler for this dynamic packet */ /** Parse handler for this dynamic packet */
DynamicPayloadPacketHandlerProc parse_packet; DynamicPayloadPacketHandlerProc parse_packet;
int (*need_keyframe)(PayloadContext *context);
struct RTPDynamicProtocolHandler *next; struct RTPDynamicProtocolHandler *next;
}; };
@ -180,6 +183,8 @@ struct RTPDemuxContext {
unsigned int packet_count; unsigned int packet_count;
unsigned int octet_count; unsigned int octet_count;
unsigned int last_octet_count; unsigned int last_octet_count;
int64_t last_feedback_time;
/* buffer for partially parsed packets */ /* buffer for partially parsed packets */
uint8_t buf[RTP_MAX_PACKET_LENGTH]; uint8_t buf[RTP_MAX_PACKET_LENGTH];

View File

@ -379,6 +379,8 @@ static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1,
get_word(buf1, sizeof(buf1), &p); /* protocol */ get_word(buf1, sizeof(buf1), &p); /* protocol */
if (!strcmp(buf1, "udp")) if (!strcmp(buf1, "udp"))
rt->transport = RTSP_TRANSPORT_RAW; rt->transport = RTSP_TRANSPORT_RAW;
else if (strstr(buf1, "/AVPF") || strstr(buf1, "/SAVPF"))
rtsp_st->feedback = 1;
/* XXX: handle list of formats */ /* XXX: handle list of formats */
get_word(buf1, sizeof(buf1), &p); /* format list */ get_word(buf1, sizeof(buf1), &p); /* format list */
@ -1936,6 +1938,12 @@ redo:
ret = ff_rdt_parse_packet(rtsp_st->transport_priv, pkt, &rt->recvbuf, len); ret = ff_rdt_parse_packet(rtsp_st->transport_priv, pkt, &rt->recvbuf, len);
} else if (rt->transport == RTSP_TRANSPORT_RTP) { } else if (rt->transport == RTSP_TRANSPORT_RTP) {
ret = ff_rtp_parse_packet(rtsp_st->transport_priv, pkt, &rt->recvbuf, len); ret = ff_rtp_parse_packet(rtsp_st->transport_priv, pkt, &rt->recvbuf, len);
if (rtsp_st->feedback) {
AVIOContext *pb = NULL;
if (rt->lower_transport == RTSP_LOWER_TRANSPORT_CUSTOM)
pb = s->pb;
ff_rtp_send_rtcp_feedback(rtsp_st->transport_priv, rtsp_st->rtp_handle, pb);
}
if (ret < 0) { if (ret < 0) {
/* Either bad packet, or a RTCP packet. Check if the /* Either bad packet, or a RTCP packet. Check if the
* first_rtcp_ntp_time field was initialized. */ * first_rtcp_ntp_time field was initialized. */

View File

@ -437,6 +437,9 @@ typedef struct RTSPStream {
/** private data associated with the dynamic protocol */ /** private data associated with the dynamic protocol */
PayloadContext *dynamic_protocol_context; PayloadContext *dynamic_protocol_context;
//@} //@}
/** Enable sending RTCP feedback messages according to RFC 4585 */
int feedback;
} RTSPStream; } RTSPStream;
void ff_rtsp_parse_line(RTSPMessageHeader *reply, const char *buf, void ff_rtsp_parse_line(RTSPMessageHeader *reply, const char *buf,

View File

@ -31,7 +31,7 @@
#define LIBAVFORMAT_VERSION_MAJOR 54 #define LIBAVFORMAT_VERSION_MAJOR 54
#define LIBAVFORMAT_VERSION_MINOR 20 #define LIBAVFORMAT_VERSION_MINOR 20
#define LIBAVFORMAT_VERSION_MICRO 3 #define LIBAVFORMAT_VERSION_MICRO 4
#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
LIBAVFORMAT_VERSION_MINOR, \ LIBAVFORMAT_VERSION_MINOR, \