/** * @file rtp.c * @brief Real-Time Protocol (RTP) demux module for VLC media player */ /***************************************************************************** * Copyright (C) 2001-2005 VLC authors and VideoLAN * Copyright © 2007-2009 Rémi Denis-Courmont * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ****************************************************************************/ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include "vlc_dtls.h" #include /* module_exists() */ #include "rtp.h" #ifdef HAVE_SRTP # include "srtp.h" # include # include #endif #include "sdp.h" #include "input.h" /* * TODO: so much stuff * - send RTCP-RR and RTCP-BYE * - multiple medias (need SDP parser, and RTCP-SR parser for lip-sync) * - support for stream_filter in case of chained demux (MPEG-TS) */ #ifndef IPPROTO_DCCP # define IPPROTO_DCCP 33 /* IANA */ #endif #ifndef IPPROTO_UDPLITE # define IPPROTO_UDPLITE 136 /* from IANA */ #endif struct vlc_rtp_es_id { struct vlc_rtp_es es; es_out_t *out; es_out_id_t *id; }; static void vlc_rtp_es_id_destroy(struct vlc_rtp_es *es) { struct vlc_rtp_es_id *ei = container_of(es, struct vlc_rtp_es_id, es); es_out_Del(ei->out, ei->id); free(ei); } static void vlc_rtp_es_id_send(struct vlc_rtp_es *es, block_t *block) { struct vlc_rtp_es_id *ei = container_of(es, struct vlc_rtp_es_id, es); /* TODO: Don't set PCR here. Breaks multiple sources (in a session) * and more importantly eventually multiple sessions. */ vlc_tick_t pcr = (block->i_dts != VLC_TICK_INVALID) ? block->i_dts : block->i_pts; if (pcr != VLC_TICK_INVALID) es_out_SetPCR(ei->out, pcr); es_out_Send(ei->out, ei->id, block); } static const struct vlc_rtp_es_operations vlc_rtp_es_id_ops = { vlc_rtp_es_id_destroy, vlc_rtp_es_id_send, }; static struct vlc_rtp_es *vlc_rtp_es_request(struct vlc_rtp_pt *pt, const es_format_t *restrict fmt) { demux_t *demux = pt->owner.data; struct vlc_rtp_es_id *ei = malloc(sizeof (*ei)); if (unlikely(ei == NULL)) return vlc_rtp_es_dummy; ei->es.ops = &vlc_rtp_es_id_ops; ei->out = demux->out; ei->id = es_out_Add(demux->out, fmt); if (ei->id == NULL) { free(ei); return vlc_rtp_es_dummy; } return &ei->es; } struct vlc_rtp_es_mux { struct vlc_rtp_es es; vlc_demux_chained_t *chained_demux; }; static void vlc_rtp_es_mux_destroy(struct vlc_rtp_es *es) { struct vlc_rtp_es_mux *em = container_of(es, struct vlc_rtp_es_mux, es); vlc_demux_chained_Delete(em->chained_demux); free(em); } static void vlc_rtp_es_mux_send(struct vlc_rtp_es *es, block_t *block) { struct vlc_rtp_es_mux *em = container_of(es, struct vlc_rtp_es_mux, es); vlc_demux_chained_Send(em->chained_demux, block); } static const struct vlc_rtp_es_operations vlc_rtp_es_mux_ops = { vlc_rtp_es_mux_destroy, vlc_rtp_es_mux_send, }; static struct vlc_rtp_es *vlc_rtp_mux_request(struct vlc_rtp_pt *pt, const char *name) { demux_t *demux = pt->owner.data; struct vlc_rtp_es_mux *em = malloc(sizeof (*em)); if (unlikely(em == NULL)) return vlc_rtp_es_dummy; em->es.ops = &vlc_rtp_es_mux_ops; em->chained_demux = vlc_demux_chained_New(VLC_OBJECT(demux), name, demux->out); if (em->chained_demux == NULL) { free(em); return NULL; } return &em->es; } static const struct vlc_rtp_pt_owner_operations vlc_rtp_pt_owner_ops = { vlc_rtp_es_request, vlc_rtp_mux_request, }; int vlc_rtp_pt_instantiate(vlc_object_t *obj, struct vlc_rtp_pt *restrict pt, const struct vlc_sdp_pt *restrict desc) { char modname[32]; int ret = VLC_ENOTSUP; if (strchr(desc->name, ',') != NULL) /* Comma has special meaning in vlc_module_match(), forbid it */ return VLC_EINVAL; if ((size_t)snprintf(modname, sizeof (modname), "%s/%s", desc->media->type, desc->name) >= sizeof (modname)) return VLC_ENOTSUP; /* Outlandish media type with long name */ module_t **mods; ssize_t n = vlc_module_match("rtp parser", modname, true, &mods, NULL); for (ssize_t i = 0; i < n; i++) { vlc_rtp_parser_cb cb = vlc_module_map(vlc_object_logger(obj), mods[i]); if (cb == NULL) continue; ret = cb(obj, pt, desc); if (ret == VLC_SUCCESS) { msg_Dbg(obj, "- module \"%s\"", module_get_name(mods[i], true)); assert(pt->ops != NULL); ret = 0; break; } } free(mods); return ret; } /** * Extracts port number from "[host]:port" or "host:port" strings, * and remove brackets from the host name. * @param phost pointer to the string upon entry, * pointer to the hostname upon return. * @return port number, 0 if missing. */ static int extract_port (char **phost) { char *host = *phost, *port; if (host[0] == '[') { host = ++*phost; /* skip '[' */ port = strchr (host, ']'); if (port) *port++ = '\0'; /* skip ']' */ } else port = strchr (host, ':'); if (port == NULL) return 0; *port++ = '\0'; /* skip ':' */ return atoi (port); } /** * Control callback */ static int Control (demux_t *demux, int query, va_list args) { switch (query) { case DEMUX_GET_PTS_DELAY: { *va_arg (args, vlc_tick_t *) = VLC_TICK_FROM_MS( var_InheritInteger (demux, "network-caching") ); return VLC_SUCCESS; } case DEMUX_CAN_PAUSE: case DEMUX_CAN_SEEK: case DEMUX_CAN_CONTROL_PACE: { bool *v = va_arg( args, bool * ); *v = false; return VLC_SUCCESS; } } switch (query) { case DEMUX_GET_POSITION: { float *v = va_arg (args, float *); *v = 0.; return VLC_SUCCESS; } case DEMUX_GET_LENGTH: case DEMUX_GET_TIME: { *va_arg (args, vlc_tick_t *) = 0; return VLC_SUCCESS; } } return VLC_EGENERIC; } /** * Releases resources */ static void Close (vlc_object_t *obj) { demux_t *demux = (demux_t *)obj; rtp_sys_t *p_sys = demux->p_sys; vlc_cancel(p_sys->thread); vlc_join(p_sys->thread, NULL); #ifdef HAVE_SRTP if (p_sys->input_sys.srtp) srtp_destroy (p_sys->input_sys.srtp); #endif rtp_session_destroy (obj->logger, p_sys->session); if (p_sys->input_sys.rtcp_sock != NULL) vlc_dtls_Close(p_sys->input_sys.rtcp_sock); vlc_dtls_Close(p_sys->input_sys.rtp_sock); } static int OpenSDP(vlc_object_t *obj) { demux_t *demux = (demux_t *)obj; uint64_t size; const unsigned char *peek; assert(demux->out != NULL); if (vlc_stream_Peek(demux->s, &peek, 3) < 3 || memcmp(peek, "v=0", 3)) return VLC_EGENERIC; /* not an SDP */ if (vlc_stream_GetSize(demux->s, &size)) size = 65536; else if (size > 65536) { msg_Err(obj, "SDP description too large: %" PRIu64 " bytes", size); return VLC_EGENERIC; } /* We must peek so that fallback to another plugin works. */ ssize_t sdplen = vlc_stream_Peek(demux->s, &peek, size); if (sdplen < 0) return sdplen; rtp_sys_t *sys = vlc_obj_malloc(obj, sizeof (*sys)); if (unlikely(sys == NULL)) return VLC_ENOMEM; sys->input_sys.rtp_sock = NULL; sys->input_sys.rtcp_sock = NULL; sys->session = NULL; #ifdef HAVE_SRTP sys->input_sys.srtp = NULL; #endif struct vlc_sdp *sdp = vlc_sdp_parse((const char *)peek, sdplen); if (sdp == NULL) { msg_Err(obj, "SDP description parse error"); return VLC_EGENERIC; } struct vlc_sdp_media *media = sdp->media; if (media == NULL || media->next != NULL) { msg_Dbg(obj, "only one SDP m= line supported"); goto error; } if (vlc_sdp_media_attr_value(media, "control") != NULL || vlc_sdp_attr_value(sdp, "control") != NULL) { msg_Dbg(obj, "RTSP not supported"); goto error; } struct vlc_sdp_conn *conn = media->conns; if (conn != NULL && conn->next != NULL) { msg_Dbg(obj, "only one SDP c= line supported"); goto error; } if (conn == NULL) conn = sdp->conn; if (conn == NULL) { msg_Err(obj, "missing SDP c= line"); goto error; } /* Determine destination port numbers */ unsigned int rtp_port, rtcp_port; if (!vlc_sdp_media_attr_present(media, "rtcp-mux")) { const char *rtcp = vlc_sdp_media_attr_value(media, "rtcp"); if (rtcp != NULL) { /* Explicit RTCP port */ char *end; unsigned long x = strtoul(rtcp, &end, 10); if (*end || x == 0 || x > 65535) { msg_Err(obj, "invalid RTCP port specification %s", rtcp); goto error; } rtp_port = media->port; rtcp_port = x; } else { /* Implicit RTCP port (next odd) */ rtp_port = (media->port + 1) & ~1; rtcp_port = media->port | 1; } } else { /* RTCP muxed on same port RTP */ rtp_port = media->port; rtcp_port = 0; } /* TODO: support other protocols */ if (strcmp(media->proto, "RTP/AVP") != 0) { msg_Dbg(obj, "unsupported protocol %s", media->proto); goto error; } /* Determine source address */ char srcbuf[256], *src = NULL; const char *sfilter = vlc_sdp_media_attr_value(media, "source-filter"); if (sfilter == NULL) sfilter = vlc_sdp_attr_value(sdp, "source-filter"); /* FIXME: handle multiple source-filter attributes, match destination, * check IP version */ if (sfilter != NULL && sscanf(sfilter, " incl IN IP%*1[46] %*s %255s", srcbuf) == 1) src = srcbuf; /* FIXME: enforce address family */ int fd = net_OpenDgram(obj, conn->addr, rtp_port, src, 0, IPPROTO_UDP); if (fd == -1) goto error; sys->input_sys.rtp_sock = vlc_datagram_CreateFD(fd); if (unlikely(sys->input_sys.rtp_sock == NULL)) { net_Close(fd); goto error; } if (rtcp_port > 0) { fd = net_OpenDgram(obj, conn->addr, rtcp_port, src, 0, IPPROTO_UDP); if (fd == -1) goto error; sys->input_sys.rtcp_sock = vlc_datagram_CreateFD(fd); if (unlikely(sys->input_sys.rtcp_sock == NULL)) { net_Close(fd); goto error; } } sys->logger = obj->logger; demux->pf_demux = NULL; demux->pf_control = Control; demux->p_sys = sys; sys->session = rtp_session_create_custom(var_InheritInteger(obj, "rtp-max-dropout"), var_InheritInteger(obj, "rtp-max-misorder"), var_InheritInteger(obj, "rtp-max-src"), vlc_tick_from_sec(var_InheritInteger(obj, "rtp-timeout"))); if (sys->session == NULL) goto error; /* Parse payload types */ const struct vlc_rtp_pt_owner pt_owner = { &vlc_rtp_pt_owner_ops, demux }; int err = vlc_rtp_add_media_types(obj, sys->session, media, &pt_owner); if (err < 0) { msg_Err(obj, "SDP description parse error"); goto error; } if (err > 0 && module_exists("live555")) /* Bail out to live555 */ goto error; if (vlc_clone(&sys->thread, rtp_dgram_thread, sys)) { rtp_session_destroy(obj->logger, sys->session); goto error; } vlc_sdp_free(sdp); return VLC_SUCCESS; error: if (sys->input_sys.rtcp_sock != NULL) vlc_dtls_Close(sys->input_sys.rtcp_sock); if (sys->input_sys.rtp_sock != NULL) vlc_dtls_Close(sys->input_sys.rtp_sock); vlc_sdp_free(sdp); return VLC_EGENERIC; } /** * Probes and initializes. */ static int OpenURL(vlc_object_t *obj) { demux_t *demux = (demux_t *)obj; int tp; /* transport protocol */ if (demux->out == NULL) return VLC_EGENERIC; if (!strcasecmp(demux->psz_name, "dccp")) tp = IPPROTO_DCCP; else if (!strcasecmp(demux->psz_name, "rtp")) tp = IPPROTO_UDP; else if (!strcasecmp(demux->psz_name, "udplite")) tp = IPPROTO_UDPLITE; else return VLC_EGENERIC; rtp_sys_t *p_sys = vlc_obj_malloc(obj, sizeof (*p_sys)); if (unlikely(p_sys == NULL)) return VLC_ENOMEM; char *tmp = strdup (demux->psz_location); if (tmp == NULL) return VLC_ENOMEM; char *shost; char *dhost = strchr (tmp, '@'); if (dhost != NULL) { *(dhost++) = '\0'; shost = tmp; } else { dhost = tmp; shost = NULL; } /* Parses the port numbers */ int sport = 0, dport = 0; if (shost != NULL) sport = extract_port (&shost); if (dhost != NULL) dport = extract_port (&dhost); if (dport == 0) dport = 5004; /* avt-profile-1 port */ int rtcp_dport = var_CreateGetInteger (obj, "rtcp-port"); /* Try to connect */ int fd = -1, rtcp_fd = -1; bool co = false; switch (tp) { case IPPROTO_UDP: case IPPROTO_UDPLITE: fd = net_OpenDgram (obj, dhost, dport, shost, sport, tp); if (fd == -1) break; if (rtcp_dport > 0) /* XXX: source port is unknown */ rtcp_fd = net_OpenDgram (obj, dhost, rtcp_dport, shost, 0, tp); break; case IPPROTO_DCCP: #ifndef SOCK_DCCP /* provisional API (FIXME) */ # ifdef __linux__ # define SOCK_DCCP 6 # endif #endif #ifdef SOCK_DCCP var_Create (obj, "dccp-service", VLC_VAR_STRING); var_SetString (obj, "dccp-service", "RTPV"); /* FIXME: RTPA? */ fd = net_Connect (obj, dhost, dport, SOCK_DCCP, tp); co = true; #else msg_Err (obj, "DCCP support not included"); #endif break; } free (tmp); if(fd == -1) return VLC_EGENERIC; p_sys->input_sys.rtp_sock = (co ? vlc_dccp_CreateFD : vlc_datagram_CreateFD)(fd); if (p_sys->input_sys.rtp_sock == NULL) { if (rtcp_fd != -1) net_Close(rtcp_fd); return VLC_EGENERIC; } net_SetCSCov (fd, -1, 12); if (rtcp_fd != -1) { p_sys->input_sys.rtcp_sock = vlc_datagram_CreateFD(rtcp_fd); if (p_sys->input_sys.rtcp_sock == NULL) net_Close (rtcp_fd); } else p_sys->input_sys.rtcp_sock = NULL; #ifdef HAVE_SRTP p_sys->input_sys.srtp = NULL; #endif p_sys->logger = obj->logger; demux->pf_demux = NULL; demux->pf_control = Control; demux->p_sys = p_sys; p_sys->session = rtp_session_create_custom( var_InheritInteger(obj, "rtp-max-dropout"), var_InheritInteger(obj, "rtp-max-misorder"), var_InheritInteger(obj, "rtp-max-src"), vlc_tick_from_sec(var_InheritInteger(obj, "rtp-timeout")) ); if (p_sys->session == NULL) goto error; const struct vlc_rtp_pt_owner pt_owner = { &vlc_rtp_pt_owner_ops, demux }; rtp_autodetect(VLC_OBJECT(demux), p_sys->session, &pt_owner); #ifdef HAVE_SRTP char *key = var_CreateGetNonEmptyString (demux, "srtp-key"); if (key) { vlc_gcrypt_init (); p_sys->input_sys.srtp = srtp_create (SRTP_ENCR_AES_CM, SRTP_AUTH_HMAC_SHA1, 10, SRTP_PRF_AES_CM, SRTP_RCC_MODE1); if (p_sys->input_sys.srtp == NULL) { free (key); goto error; } char *salt = var_CreateGetNonEmptyString (demux, "srtp-salt"); int val = srtp_setkeystring (p_sys->input_sys.srtp, key, salt ? salt : ""); free (salt); free (key); if (val) { msg_Err (obj, "bad SRTP key/salt combination (%s)", vlc_strerror_c(val)); goto error; } } #endif if (vlc_clone (&p_sys->thread, rtp_dgram_thread, p_sys)) goto error; return VLC_SUCCESS; error: #ifdef HAVE_SRTP if (p_sys->input_sys.srtp != NULL) srtp_destroy(p_sys->input_sys.srtp); #endif if (p_sys->session != NULL) rtp_session_destroy(obj->logger, p_sys->session); if (p_sys->input_sys.rtcp_sock != NULL) vlc_dtls_Close(p_sys->input_sys.rtcp_sock); vlc_dtls_Close(p_sys->input_sys.rtp_sock); return VLC_EGENERIC; } #define RTCP_PORT_TEXT N_("RTCP (local) port") #define RTCP_PORT_LONGTEXT N_( \ "RTCP packets will be received on this transport protocol port. " \ "If zero, multiplexed RTP/RTCP is used.") #define SRTP_KEY_TEXT N_("SRTP key (hexadecimal)") #define SRTP_KEY_LONGTEXT N_( \ "RTP packets will be authenticated and deciphered "\ "with this Secure RTP master shared secret key. "\ "This must be a 32-character-long hexadecimal string.") #define SRTP_SALT_TEXT N_("SRTP salt (hexadecimal)") #define SRTP_SALT_LONGTEXT N_( \ "Secure RTP requires a (non-secret) master salt value. " \ "This must be a 28-character-long hexadecimal string.") #define RTP_MAX_SRC_TEXT N_("Maximum RTP sources") #define RTP_MAX_SRC_LONGTEXT N_( \ "How many distinct active RTP sources are allowed at a time." ) #define RTP_TIMEOUT_TEXT N_("RTP source timeout (sec)") #define RTP_TIMEOUT_LONGTEXT N_( \ "How long to wait for any packet before a source is expired.") #define RTP_MAX_DROPOUT_TEXT N_("Maximum RTP sequence number dropout") #define RTP_MAX_DROPOUT_LONGTEXT N_( \ "RTP packets will be discarded if they are too much ahead (i.e. in the " \ "future) by this many packets from the last received packet." ) #define RTP_MAX_MISORDER_TEXT N_("Maximum RTP sequence number misordering") #define RTP_MAX_MISORDER_LONGTEXT N_( \ "RTP packets will be discarded if they are too far behind (i.e. in the " \ "past) by this many packets from the last received packet." ) /* * Module descriptor */ vlc_module_begin() set_shortname(N_("RTP")) set_description(N_("Real-Time Protocol (RTP) input")) set_subcategory(SUBCAT_INPUT_DEMUX) set_capability("demux", 55) set_callbacks(OpenSDP, Close) add_submodule() set_capability("access", 0) set_callbacks(OpenURL, Close) add_integer("rtcp-port", 0, RTCP_PORT_TEXT, RTCP_PORT_LONGTEXT) change_integer_range(0, 65535) change_safe() #ifdef HAVE_SRTP add_string ("srtp-key", "", SRTP_KEY_TEXT, SRTP_KEY_LONGTEXT) change_safe () add_string("srtp-salt", "", SRTP_SALT_TEXT, SRTP_SALT_LONGTEXT) change_safe() #endif add_integer("rtp-max-src", RTP_MAX_SRC_DEFAULT, RTP_MAX_SRC_TEXT, RTP_MAX_SRC_LONGTEXT) change_integer_range (1, 255) add_integer("rtp-timeout", RTP_MAX_TIMEOUT_DEFAULT, RTP_TIMEOUT_TEXT, RTP_TIMEOUT_LONGTEXT) add_integer("rtp-max-dropout", RTP_MAX_DROPOUT_DEFAULT, RTP_MAX_DROPOUT_TEXT, RTP_MAX_DROPOUT_LONGTEXT) change_integer_range (0, 32767) add_integer("rtp-max-misorder", RTP_MAX_MISORDER_DEFAULT, RTP_MAX_MISORDER_TEXT, RTP_MAX_MISORDER_LONGTEXT) change_integer_range (0, 32767) add_obsolete_string("rtp-dynamic-pt") /* since 4.0.0 */ /*add_shortcut ("sctp")*/ add_shortcut("dccp", "rtp", "udplite") vlc_module_end()