You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

613 lines
21KB

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