1 /*
2  * RTP network protocol
3  * Copyright (c) 2002 Fabrice Bellard
4  *
5  * This file is part of FFmpeg.
6  *
7  * FFmpeg is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * FFmpeg is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with FFmpeg; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20  */
21 
22 /**
23  * @file
24  * RTP protocol
25  */
26 
27 #include "libavutil/parseutils.h"
28 #include "libavutil/avstring.h"
29 #include "libavutil/opt.h"
30 #include "avformat.h"
31 #include "avio_internal.h"
32 #include "rtp.h"
33 #include "rtpproto.h"
34 #include "url.h"
35 #include "ip.h"
36 
37 #include <stdarg.h>
38 #include "internal.h"
39 #include "network.h"
40 #include "os_support.h"
41 #include <fcntl.h>
42 #if HAVE_POLL_H
43 #include <poll.h>
44 #endif
45 
46 typedef struct RTPContext {
47     const AVClass *class;
48     URLContext *rtp_hd, *rtcp_hd, *fec_hd;
49     int rtp_fd, rtcp_fd;
50     IPSourceFilters filters;
51     int write_to_source;
52     struct sockaddr_storage last_rtp_source, last_rtcp_source;
53     socklen_t last_rtp_source_len, last_rtcp_source_len;
54     int ttl;
55     int buffer_size;
56     int rtcp_port, local_rtpport, local_rtcpport;
57     int connect;
58     int pkt_size;
59     int dscp;
60     char *sources;
61     char *block;
62     char *fec_options_str;
63     int64_t rw_timeout;
64     char *localaddr;
65 } RTPContext;
66 
67 #define OFFSET(x) offsetof(RTPContext, x)
68 #define D AV_OPT_FLAG_DECODING_PARAM
69 #define E AV_OPT_FLAG_ENCODING_PARAM
70 static const AVOption options[] = {
71     { "ttl",                "Time to live (multicast only)",                                    OFFSET(ttl),             AV_OPT_TYPE_INT,    { .i64 = -1 },    -1, 255,     .flags = D|E },
72     { "buffer_size",        "Send/Receive buffer size (in bytes)",                              OFFSET(buffer_size),     AV_OPT_TYPE_INT,    { .i64 = -1 },    -1, INT_MAX, .flags = D|E },
73     { "rtcp_port",          "Custom rtcp port",                                                 OFFSET(rtcp_port),       AV_OPT_TYPE_INT,    { .i64 = -1 },    -1, INT_MAX, .flags = D|E },
74     { "local_rtpport",      "Local rtp port",                                                   OFFSET(local_rtpport),   AV_OPT_TYPE_INT,    { .i64 = -1 },    -1, INT_MAX, .flags = D|E },
75     { "local_rtcpport",     "Local rtcp port",                                                  OFFSET(local_rtcpport),  AV_OPT_TYPE_INT,    { .i64 = -1 },    -1, INT_MAX, .flags = D|E },
76     { "connect",            "Connect socket",                                                   OFFSET(connect),         AV_OPT_TYPE_BOOL,   { .i64 =  0 },     0, 1,       .flags = D|E },
77     { "write_to_source",    "Send packets to the source address of the latest received packet", OFFSET(write_to_source), AV_OPT_TYPE_BOOL,   { .i64 =  0 },     0, 1,       .flags = D|E },
78     { "pkt_size",           "Maximum packet size",                                              OFFSET(pkt_size),        AV_OPT_TYPE_INT,    { .i64 = -1 },    -1, INT_MAX, .flags = D|E },
79     { "dscp",               "DSCP class",                                                       OFFSET(dscp),            AV_OPT_TYPE_INT,    { .i64 = -1 },    -1, INT_MAX, .flags = D|E },
80     { "timeout",            "set timeout (in microseconds) of socket I/O operations",           OFFSET(rw_timeout),      AV_OPT_TYPE_INT64,  { .i64 = -1 },    -1, INT64_MAX, .flags = D|E },
81     { "sources",            "Source list",                                                      OFFSET(sources),         AV_OPT_TYPE_STRING, { .str = NULL },               .flags = D|E },
82     { "block",              "Block list",                                                       OFFSET(block),           AV_OPT_TYPE_STRING, { .str = NULL },               .flags = D|E },
83     { "fec",                "FEC",                                                              OFFSET(fec_options_str), AV_OPT_TYPE_STRING, { .str = NULL },               .flags = E },
84     { "localaddr",          "Local address",                                                    OFFSET(localaddr),       AV_OPT_TYPE_STRING, { .str = NULL },               .flags = D|E },
85     { NULL }
86 };
87 
88 static const AVClass rtp_class = {
89     .class_name = "rtp",
90     .item_name  = av_default_item_name,
91     .option     = options,
92     .version    = LIBAVUTIL_VERSION_INT,
93 };
94 
95 /**
96  * If no filename is given to av_open_input_file because you want to
97  * get the local port first, then you must call this function to set
98  * the remote server address.
99  *
100  * @param h media file context
101  * @param uri of the remote server
102  * @return zero if no error.
103  */
104 
ff_rtp_set_remote_url(URLContext *h, const char *uri)105 int ff_rtp_set_remote_url(URLContext *h, const char *uri)
106 {
107     RTPContext *s = h->priv_data;
108     char hostname[256];
109     int port, rtcp_port;
110     const char *p;
111 
112     char buf[1024];
113     char path[1024];
114 
115     av_url_split(NULL, 0, NULL, 0, hostname, sizeof(hostname), &port,
116                  path, sizeof(path), uri);
117     rtcp_port = port + 1;
118 
119     p = strchr(uri, '?');
120     if (p) {
121         if (av_find_info_tag(buf, sizeof(buf), "rtcpport", p)) {
122             rtcp_port = strtol(buf, NULL, 10);
123         }
124     }
125 
126     ff_url_join(buf, sizeof(buf), "udp", NULL, hostname, port, "%s", path);
127     ff_udp_set_remote_url(s->rtp_hd, buf);
128 
129     ff_url_join(buf, sizeof(buf), "udp", NULL, hostname, rtcp_port, "%s", path);
130     ff_udp_set_remote_url(s->rtcp_hd, buf);
131     return 0;
132 }
133 
get_port(const struct sockaddr_storage *ss)134 static int get_port(const struct sockaddr_storage *ss)
135 {
136     if (ss->ss_family == AF_INET)
137         return ntohs(((const struct sockaddr_in *)ss)->sin_port);
138 #if HAVE_STRUCT_SOCKADDR_IN6
139     if (ss->ss_family == AF_INET6)
140         return ntohs(((const struct sockaddr_in6 *)ss)->sin6_port);
141 #endif
142     return 0;
143 }
144 
set_port(struct sockaddr_storage *ss, int port)145 static void set_port(struct sockaddr_storage *ss, int port)
146 {
147     if (ss->ss_family == AF_INET)
148         ((struct sockaddr_in *)ss)->sin_port = htons(port);
149 #if HAVE_STRUCT_SOCKADDR_IN6
150     else if (ss->ss_family == AF_INET6)
151         ((struct sockaddr_in6 *)ss)->sin6_port = htons(port);
152 #endif
153 }
154 
155 /**
156  * add option to url of the form:
157  * "http://host:port/path?option1=val1&option2=val2...
158  */
159 
url_add_option(char *buf, int buf_size, const char *fmt, ...)160 static av_printf_format(3, 4) void url_add_option(char *buf, int buf_size, const char *fmt, ...)
161 {
162     char buf1[1024];
163     va_list ap;
164 
165     va_start(ap, fmt);
166     if (strchr(buf, '?'))
167         av_strlcat(buf, "&", buf_size);
168     else
169         av_strlcat(buf, "?", buf_size);
170     vsnprintf(buf1, sizeof(buf1), fmt, ap);
171     av_strlcat(buf, buf1, buf_size);
172     va_end(ap);
173 }
174 
build_udp_url(RTPContext *s, char *buf, int buf_size, const char *hostname, const char *localaddr, int port, int local_port, const char *include_sources, const char *exclude_sources)175 static void build_udp_url(RTPContext *s,
176                           char *buf, int buf_size,
177                           const char *hostname,
178                           const char *localaddr,
179                           int port, int local_port,
180                           const char *include_sources,
181                           const char *exclude_sources)
182 {
183     ff_url_join(buf, buf_size, "udp", NULL, hostname, port, NULL);
184     if (local_port >= 0)
185         url_add_option(buf, buf_size, "localport=%d", local_port);
186     if (s->ttl >= 0)
187         url_add_option(buf, buf_size, "ttl=%d", s->ttl);
188     if (s->buffer_size >= 0)
189         url_add_option(buf, buf_size, "buffer_size=%d", s->buffer_size);
190     if (s->pkt_size >= 0)
191         url_add_option(buf, buf_size, "pkt_size=%d", s->pkt_size);
192     if (s->connect)
193         url_add_option(buf, buf_size, "connect=1");
194     if (s->dscp >= 0)
195         url_add_option(buf, buf_size, "dscp=%d", s->dscp);
196     url_add_option(buf, buf_size, "fifo_size=0");
197     if (include_sources && include_sources[0])
198         url_add_option(buf, buf_size, "sources=%s", include_sources);
199     if (exclude_sources && exclude_sources[0])
200         url_add_option(buf, buf_size, "block=%s", exclude_sources);
201     if (localaddr && localaddr[0])
202         url_add_option(buf, buf_size, "localaddr=%s", localaddr);
203 }
204 
205 /**
206  * url syntax: rtp://host:port[?option=val...]
207  * option: 'ttl=n'            : set the ttl value (for multicast only)
208  *         'rtcpport=n'       : set the remote rtcp port to n
209  *         'localrtpport=n'   : set the local rtp port to n
210  *         'localrtcpport=n'  : set the local rtcp port to n
211  *         'pkt_size=n'       : set max packet size
212  *         'connect=0/1'      : do a connect() on the UDP socket
213  *         'sources=ip[,ip]'  : list allowed source IP addresses
214  *         'block=ip[,ip]'    : list disallowed source IP addresses
215  *         'write_to_source=0/1' : send packets to the source address of the latest received packet
216  *         'dscp=n'           : set DSCP value to n (QoS)
217  * deprecated option:
218  *         'localport=n'      : set the local port to n
219  *
220  * if rtcpport isn't set the rtcp port will be the rtp port + 1
221  * if local rtp port isn't set any available port will be used for the local
222  * rtp and rtcp ports
223  * if the local rtcp port is not set it will be the local rtp port + 1
224  */
225 
rtp_open(URLContext *h, const char *uri, int flags)226 static int rtp_open(URLContext *h, const char *uri, int flags)
227 {
228     RTPContext *s = h->priv_data;
229     AVDictionary *fec_opts = NULL;
230     int rtp_port;
231     char hostname[256], include_sources[1024] = "", exclude_sources[1024] = "";
232     char *sources = include_sources, *block = exclude_sources;
233     char *fec_protocol = NULL;
234     char buf[1024];
235     char path[1024];
236     const char *p;
237     int i, max_retry_count = 3;
238     int rtcpflags;
239 
240     av_url_split(NULL, 0, NULL, 0, hostname, sizeof(hostname), &rtp_port,
241                  path, sizeof(path), uri);
242     /* extract parameters */
243     if (s->rtcp_port < 0)
244         s->rtcp_port = rtp_port + 1;
245 
246     p = strchr(uri, '?');
247     if (p) {
248         if (av_find_info_tag(buf, sizeof(buf), "ttl", p)) {
249             s->ttl = strtol(buf, NULL, 10);
250         }
251         if (av_find_info_tag(buf, sizeof(buf), "rtcpport", p)) {
252             s->rtcp_port = strtol(buf, NULL, 10);
253         }
254         if (av_find_info_tag(buf, sizeof(buf), "localport", p)) {
255             s->local_rtpport = strtol(buf, NULL, 10);
256         }
257         if (av_find_info_tag(buf, sizeof(buf), "localrtpport", p)) {
258             s->local_rtpport = strtol(buf, NULL, 10);
259         }
260         if (av_find_info_tag(buf, sizeof(buf), "localrtcpport", p)) {
261             s->local_rtcpport = strtol(buf, NULL, 10);
262         }
263         if (av_find_info_tag(buf, sizeof(buf), "pkt_size", p)) {
264             s->pkt_size = strtol(buf, NULL, 10);
265         }
266         if (av_find_info_tag(buf, sizeof(buf), "connect", p)) {
267             s->connect = strtol(buf, NULL, 10);
268         }
269         if (av_find_info_tag(buf, sizeof(buf), "write_to_source", p)) {
270             s->write_to_source = strtol(buf, NULL, 10);
271         }
272         if (av_find_info_tag(buf, sizeof(buf), "dscp", p)) {
273             s->dscp = strtol(buf, NULL, 10);
274         }
275         if (av_find_info_tag(buf, sizeof(buf), "timeout", p)) {
276             s->rw_timeout = strtol(buf, NULL, 10);
277         }
278         if (av_find_info_tag(buf, sizeof(buf), "sources", p)) {
279             av_strlcpy(include_sources, buf, sizeof(include_sources));
280             ff_ip_parse_sources(h, buf, &s->filters);
281         } else {
282             ff_ip_parse_sources(h, s->sources, &s->filters);
283             sources = s->sources;
284         }
285         if (av_find_info_tag(buf, sizeof(buf), "block", p)) {
286             av_strlcpy(exclude_sources, buf, sizeof(exclude_sources));
287             ff_ip_parse_blocks(h, buf, &s->filters);
288         } else {
289             ff_ip_parse_blocks(h, s->block, &s->filters);
290             block = s->block;
291         }
292         if (av_find_info_tag(buf, sizeof(buf), "localaddr", p)) {
293             av_freep(&s->localaddr);
294             s->localaddr = av_strdup(buf);
295             if (!s->localaddr)
296                 goto fail;
297         }
298     }
299     if (s->rw_timeout >= 0)
300         h->rw_timeout = s->rw_timeout;
301 
302     if (s->fec_options_str) {
303         p = s->fec_options_str;
304 
305         if (!(fec_protocol = av_get_token(&p, "="))) {
306             av_log(h, AV_LOG_ERROR, "Failed to parse the FEC protocol value\n");
307             goto fail;
308         }
309         if (strcmp(fec_protocol, "prompeg")) {
310             av_log(h, AV_LOG_ERROR, "Unsupported FEC protocol %s\n", fec_protocol);
311             goto fail;
312         }
313 
314         p = s->fec_options_str + strlen(fec_protocol);
315         while (*p && *p == '=') p++;
316 
317         if (av_dict_parse_string(&fec_opts, p, "=", ":", 0) < 0) {
318             av_log(h, AV_LOG_ERROR, "Failed to parse the FEC options\n");
319             goto fail;
320         }
321         if (s->ttl > 0) {
322             av_dict_set_int(&fec_opts, "ttl", s->ttl, 0);
323         }
324     }
325 
326     for (i = 0; i < max_retry_count; i++) {
327         build_udp_url(s, buf, sizeof(buf),
328                       hostname, s->localaddr, rtp_port, s->local_rtpport,
329                       sources, block);
330         if (ffurl_open_whitelist(&s->rtp_hd, buf, flags, &h->interrupt_callback,
331                                  NULL, h->protocol_whitelist, h->protocol_blacklist, h) < 0)
332             goto fail;
333         s->local_rtpport = ff_udp_get_local_port(s->rtp_hd);
334         if(s->local_rtpport == 65535) {
335             s->local_rtpport = -1;
336             continue;
337         }
338         rtcpflags = flags | AVIO_FLAG_WRITE;
339         if (s->local_rtcpport < 0) {
340             s->local_rtcpport = s->local_rtpport + 1;
341             build_udp_url(s, buf, sizeof(buf),
342                           hostname, s->localaddr, s->rtcp_port, s->local_rtcpport,
343                           sources, block);
344             if (ffurl_open_whitelist(&s->rtcp_hd, buf, rtcpflags,
345                                      &h->interrupt_callback, NULL,
346                                      h->protocol_whitelist, h->protocol_blacklist, h) < 0) {
347                 s->local_rtpport = s->local_rtcpport = -1;
348                 continue;
349             }
350             break;
351         }
352         build_udp_url(s, buf, sizeof(buf),
353                       hostname, s->localaddr, s->rtcp_port, s->local_rtcpport,
354                       sources, block);
355         if (ffurl_open_whitelist(&s->rtcp_hd, buf, rtcpflags, &h->interrupt_callback,
356                                  NULL, h->protocol_whitelist, h->protocol_blacklist, h) < 0)
357             goto fail;
358         break;
359     }
360 
361     s->fec_hd = NULL;
362     if (fec_protocol) {
363         ff_url_join(buf, sizeof(buf), fec_protocol, NULL, hostname, rtp_port, NULL);
364         if (ffurl_open_whitelist(&s->fec_hd, buf, flags, &h->interrupt_callback,
365                              &fec_opts, h->protocol_whitelist, h->protocol_blacklist, h) < 0)
366             goto fail;
367     }
368 
369     /* just to ease handle access. XXX: need to suppress direct handle
370        access */
371     s->rtp_fd = ffurl_get_file_handle(s->rtp_hd);
372     s->rtcp_fd = ffurl_get_file_handle(s->rtcp_hd);
373 
374     h->max_packet_size = s->rtp_hd->max_packet_size;
375     h->is_streamed = 1;
376 
377     av_free(fec_protocol);
378     av_dict_free(&fec_opts);
379 
380     return 0;
381 
382  fail:
383     ffurl_closep(&s->rtp_hd);
384     ffurl_closep(&s->rtcp_hd);
385     ffurl_closep(&s->fec_hd);
386     av_free(fec_protocol);
387     av_dict_free(&fec_opts);
388     return AVERROR(EIO);
389 }
390 
rtp_read(URLContext *h, uint8_t *buf, int size)391 static int rtp_read(URLContext *h, uint8_t *buf, int size)
392 {
393     RTPContext *s = h->priv_data;
394     int len, n, i;
395     struct pollfd p[2] = {{s->rtp_fd, POLLIN, 0}, {s->rtcp_fd, POLLIN, 0}};
396     int poll_delay = h->flags & AVIO_FLAG_NONBLOCK ? 0 : POLLING_TIME;
397     struct sockaddr_storage *addrs[2] = { &s->last_rtp_source, &s->last_rtcp_source };
398     socklen_t *addr_lens[2] = { &s->last_rtp_source_len, &s->last_rtcp_source_len };
399     int runs = h->rw_timeout / 1000 / POLLING_TIME;
400 
401     for(;;) {
402         if (ff_check_interrupt(&h->interrupt_callback))
403             return AVERROR_EXIT;
404         n = poll(p, 2, poll_delay);
405         if (n > 0) {
406             /* first try RTCP, then RTP */
407             for (i = 1; i >= 0; i--) {
408                 if (!(p[i].revents & POLLIN))
409                     continue;
410                 *addr_lens[i] = sizeof(*addrs[i]);
411                 len = recvfrom(p[i].fd, buf, size, 0,
412                                 (struct sockaddr *)addrs[i], addr_lens[i]);
413                 if (len < 0) {
414                     if (ff_neterrno() == AVERROR(EAGAIN) ||
415                         ff_neterrno() == AVERROR(EINTR))
416                         continue;
417                     return AVERROR(EIO);
418                 }
419                 if (ff_ip_check_source_lists(addrs[i], &s->filters))
420                     continue;
421                 return len;
422             }
423         } else if (n == 0 && h->rw_timeout > 0 && --runs <= 0) {
424             return AVERROR(ETIMEDOUT);
425         } else if (n < 0) {
426             if (ff_neterrno() == AVERROR(EINTR))
427                 continue;
428             return AVERROR(EIO);
429         }
430         if (h->flags & AVIO_FLAG_NONBLOCK)
431             return AVERROR(EAGAIN);
432     }
433 }
434 
rtp_write(URLContext *h, const uint8_t *buf, int size)435 static int rtp_write(URLContext *h, const uint8_t *buf, int size)
436 {
437     RTPContext *s = h->priv_data;
438     int ret, ret_fec;
439     URLContext *hd;
440 
441     if (size < 2)
442         return AVERROR(EINVAL);
443 
444     if ((buf[0] & 0xc0) != (RTP_VERSION << 6))
445         av_log(h, AV_LOG_WARNING, "Data doesn't look like RTP packets, "
446                                   "make sure the RTP muxer is used\n");
447 
448     if (s->write_to_source) {
449         int fd;
450         struct sockaddr_storage *source, temp_source;
451         socklen_t *source_len, temp_len;
452         if (!s->last_rtp_source.ss_family && !s->last_rtcp_source.ss_family) {
453             av_log(h, AV_LOG_ERROR,
454                    "Unable to send packet to source, no packets received yet\n");
455             // Intentionally not returning an error here
456             return size;
457         }
458 
459         if (RTP_PT_IS_RTCP(buf[1])) {
460             fd = s->rtcp_fd;
461             source     = &s->last_rtcp_source;
462             source_len = &s->last_rtcp_source_len;
463         } else {
464             fd = s->rtp_fd;
465             source     = &s->last_rtp_source;
466             source_len = &s->last_rtp_source_len;
467         }
468         if (!source->ss_family) {
469             source      = &temp_source;
470             source_len  = &temp_len;
471             if (RTP_PT_IS_RTCP(buf[1])) {
472                 temp_source = s->last_rtp_source;
473                 temp_len    = s->last_rtp_source_len;
474                 set_port(source, get_port(source) + 1);
475                 av_log(h, AV_LOG_INFO,
476                        "Not received any RTCP packets yet, inferring peer port "
477                        "from the RTP port\n");
478             } else {
479                 temp_source = s->last_rtcp_source;
480                 temp_len    = s->last_rtcp_source_len;
481                 set_port(source, get_port(source) - 1);
482                 av_log(h, AV_LOG_INFO,
483                        "Not received any RTP packets yet, inferring peer port "
484                        "from the RTCP port\n");
485             }
486         }
487 
488         if (!(h->flags & AVIO_FLAG_NONBLOCK)) {
489             ret = ff_network_wait_fd(fd, 1);
490             if (ret < 0)
491                 return ret;
492         }
493         ret = sendto(fd, buf, size, 0, (struct sockaddr *) source,
494                      *source_len);
495 
496         return ret < 0 ? ff_neterrno() : ret;
497     }
498 
499     if (RTP_PT_IS_RTCP(buf[1])) {
500         /* RTCP payload type */
501         hd = s->rtcp_hd;
502     } else {
503         /* RTP payload type */
504         hd = s->rtp_hd;
505     }
506 
507     if ((ret = ffurl_write(hd, buf, size)) < 0) {
508         return ret;
509     }
510 
511     if (s->fec_hd && !RTP_PT_IS_RTCP(buf[1])) {
512         if ((ret_fec = ffurl_write(s->fec_hd, buf, size)) < 0) {
513             av_log(h, AV_LOG_ERROR, "Failed to send FEC\n");
514             return ret_fec;
515         }
516     }
517 
518     return ret;
519 }
520 
rtp_close(URLContext *h)521 static int rtp_close(URLContext *h)
522 {
523     RTPContext *s = h->priv_data;
524 
525     ff_ip_reset_filters(&s->filters);
526 
527     ffurl_closep(&s->rtp_hd);
528     ffurl_closep(&s->rtcp_hd);
529     ffurl_closep(&s->fec_hd);
530     return 0;
531 }
532 
533 /**
534  * Return the local rtp port used by the RTP connection
535  * @param h media file context
536  * @return the local port number
537  */
538 
ff_rtp_get_local_rtp_port(URLContext *h)539 int ff_rtp_get_local_rtp_port(URLContext *h)
540 {
541     RTPContext *s = h->priv_data;
542     return ff_udp_get_local_port(s->rtp_hd);
543 }
544 
545 /**
546  * Return the local rtcp port used by the RTP connection
547  * @param h media file context
548  * @return the local port number
549  */
550 
rtp_get_file_handle(URLContext *h)551 static int rtp_get_file_handle(URLContext *h)
552 {
553     RTPContext *s = h->priv_data;
554     return s->rtp_fd;
555 }
556 
rtp_get_multi_file_handle(URLContext *h, int **handles, int *numhandles)557 static int rtp_get_multi_file_handle(URLContext *h, int **handles,
558                                      int *numhandles)
559 {
560     RTPContext *s = h->priv_data;
561     int *hs       = *handles = av_malloc(sizeof(**handles) * 2);
562     if (!hs)
563         return AVERROR(ENOMEM);
564     hs[0] = s->rtp_fd;
565     hs[1] = s->rtcp_fd;
566     *numhandles = 2;
567     return 0;
568 }
569 
570 const URLProtocol ff_rtp_protocol = {
571     .name                      = "rtp",
572     .url_open                  = rtp_open,
573     .url_read                  = rtp_read,
574     .url_write                 = rtp_write,
575     .url_close                 = rtp_close,
576     .url_get_file_handle       = rtp_get_file_handle,
577     .url_get_multi_file_handle = rtp_get_multi_file_handle,
578     .priv_data_size            = sizeof(RTPContext),
579     .flags                     = URL_PROTOCOL_FLAG_NETWORK,
580     .priv_data_class           = &rtp_class,
581 };
582