/** * @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 #include /* aout_FormatPrepare() */ #include "rtp.h" #ifdef HAVE_SRTP # include # include # include #endif #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." ) #define RTP_DYNAMIC_PT_TEXT N_("RTP payload format assumed for dynamic " \ "payloads") #define RTP_DYNAMIC_PT_LONGTEXT N_( \ "This payload format will be assumed for dynamic payload types " \ "(between 96 and 127) if it can't be determined otherwise with " \ "out-of-band mappings (SDP)" ) static const char *const dynamic_pt_list[] = { "theora" }; static const char *const dynamic_pt_list_text[] = { "Theora Encoded Video" }; static int Open (vlc_object_t *); static void Close (vlc_object_t *); /* * Module descriptor */ vlc_module_begin () set_shortname (N_("RTP")) set_description (N_("Real-Time Protocol (RTP) input")) set_category (CAT_INPUT) set_subcategory (SUBCAT_INPUT_DEMUX) set_capability ("access_demux", 0) set_callbacks (Open, Close) add_integer ("rtcp-port", 0, RTCP_PORT_TEXT, RTCP_PORT_LONGTEXT, false) change_integer_range (0, 65535) change_safe () #ifdef HAVE_SRTP add_string ("srtp-key", "", SRTP_KEY_TEXT, SRTP_KEY_LONGTEXT, false) change_safe () add_string ("srtp-salt", "", SRTP_SALT_TEXT, SRTP_SALT_LONGTEXT, false) change_safe () #endif add_integer ("rtp-max-src", 1, RTP_MAX_SRC_TEXT, RTP_MAX_SRC_LONGTEXT, true) change_integer_range (1, 255) add_integer ("rtp-timeout", 5, RTP_TIMEOUT_TEXT, RTP_TIMEOUT_LONGTEXT, true) add_integer ("rtp-max-dropout", 3000, RTP_MAX_DROPOUT_TEXT, RTP_MAX_DROPOUT_LONGTEXT, true) change_integer_range (0, 32767) add_integer ("rtp-max-misorder", 100, RTP_MAX_MISORDER_TEXT, RTP_MAX_MISORDER_LONGTEXT, true) change_integer_range (0, 32767) add_string ("rtp-dynamic-pt", NULL, RTP_DYNAMIC_PT_TEXT, RTP_DYNAMIC_PT_LONGTEXT, true) change_string_list (dynamic_pt_list, dynamic_pt_list_text) /*add_shortcut ("sctp")*/ add_shortcut ("dccp", "rtptcp", /* "tcp" is already taken :( */ "rtp", "udplite") vlc_module_end () /* * TODO: so much stuff * - send RTCP-RR and RTCP-BYE * - dynamic payload types (need SDP parser) * - 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 /* * Local prototypes */ static int Control (demux_t *, int i_query, va_list args); static int extract_port (char **phost); /** * Probes and initializes. */ static int Open (vlc_object_t *obj) { demux_t *demux = (demux_t *)obj; int tp; /* transport protocol */ if (!strcmp (demux->psz_access, "dccp")) tp = IPPROTO_DCCP; else if (!strcmp (demux->psz_access, "rtptcp")) tp = IPPROTO_TCP; else if (!strcmp (demux->psz_access, "rtp")) tp = IPPROTO_UDP; else if (!strcmp (demux->psz_access, "udplite")) tp = IPPROTO_UDPLITE; else return VLC_EGENERIC; 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; 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); #else msg_Err (obj, "DCCP support not included"); #endif break; case IPPROTO_TCP: fd = net_Connect (obj, dhost, dport, SOCK_STREAM, tp); break; } free (tmp); if (fd == -1) { if (rtcp_fd != -1) net_Close(rtcp_fd); return VLC_EGENERIC; } net_SetCSCov (fd, -1, 12); /* Initializes demux */ demux_sys_t *p_sys = malloc (sizeof (*p_sys)); if (p_sys == NULL) { net_Close (fd); if (rtcp_fd != -1) net_Close (rtcp_fd); return VLC_EGENERIC; } p_sys->chained_demux = NULL; #ifdef HAVE_SRTP p_sys->srtp = NULL; #endif p_sys->fd = fd; p_sys->rtcp_fd = rtcp_fd; p_sys->max_src = var_CreateGetInteger (obj, "rtp-max-src"); p_sys->timeout = var_CreateGetInteger (obj, "rtp-timeout") * CLOCK_FREQ; p_sys->max_dropout = var_CreateGetInteger (obj, "rtp-max-dropout"); p_sys->max_misorder = var_CreateGetInteger (obj, "rtp-max-misorder"); p_sys->thread_ready = false; p_sys->autodetect = true; demux->pf_demux = NULL; demux->pf_control = Control; demux->p_sys = p_sys; p_sys->session = rtp_session_create (demux); if (p_sys->session == NULL) goto error; #ifdef HAVE_SRTP char *key = var_CreateGetNonEmptyString (demux, "srtp-key"); if (key) { vlc_gcrypt_init (); p_sys->srtp = srtp_create (SRTP_ENCR_AES_CM, SRTP_AUTH_HMAC_SHA1, 10, SRTP_PRF_AES_CM, SRTP_RCC_MODE1); if (p_sys->srtp == NULL) { free (key); goto error; } char *salt = var_CreateGetNonEmptyString (demux, "srtp-salt"); int val = srtp_setkeystring (p_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, (tp != IPPROTO_TCP) ? rtp_dgram_thread : rtp_stream_thread, demux, VLC_THREAD_PRIORITY_INPUT)) goto error; p_sys->thread_ready = true; return VLC_SUCCESS; error: Close (obj); return VLC_EGENERIC; } /** * Releases resources */ static void Close (vlc_object_t *obj) { demux_t *demux = (demux_t *)obj; demux_sys_t *p_sys = demux->p_sys; if (p_sys->thread_ready) { vlc_cancel (p_sys->thread); vlc_join (p_sys->thread, NULL); } #ifdef HAVE_SRTP if (p_sys->srtp) srtp_destroy (p_sys->srtp); #endif if (p_sys->session) rtp_session_destroy (demux, p_sys->session); if (p_sys->rtcp_fd != -1) net_Close (p_sys->rtcp_fd); net_Close (p_sys->fd); free (p_sys); } /** * 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) { demux_sys_t *sys = demux->p_sys; switch (query) { case DEMUX_GET_PTS_DELAY: { int64_t *v = va_arg (args, int64_t *); *v = INT64_C(1000) * 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; } } if (sys->chained_demux != NULL) return vlc_demux_chained_ControlVa (sys->chained_demux, query, args); 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: { int64_t *v = va_arg (args, int64_t *); *v = 0; return VLC_SUCCESS; } } return VLC_EGENERIC; } /* * Generic packet handlers */ void *codec_init (demux_t *demux, es_format_t *fmt) { if (fmt->i_cat == AUDIO_ES) aout_FormatPrepare (&fmt->audio); return es_out_Add (demux->out, fmt); } void codec_destroy (demux_t *demux, void *data) { if (data) es_out_Del (demux->out, (es_out_id_t *)data); } /* Send a packet to decoder */ void codec_decode (demux_t *demux, void *data, block_t *block) { if (data) { block->i_dts = VLC_TICK_INVALID; /* RTP does not specify this */ es_out_SetPCR(demux->out, block->i_pts); es_out_Send (demux->out, (es_out_id_t *)data, block); } else block_Release (block); } static void *stream_init (demux_t *demux, const char *name) { demux_sys_t *p_sys = demux->p_sys; if (p_sys->chained_demux != NULL) return NULL; p_sys->chained_demux = vlc_demux_chained_New(VLC_OBJECT(demux), name, demux->out); return p_sys->chained_demux; } static void stream_destroy (demux_t *demux, void *data) { demux_sys_t *p_sys = demux->p_sys; if (data) { vlc_demux_chained_Delete(data); p_sys->chained_demux = NULL; } } static void stream_header (demux_t *demux, void *data, block_t *block) { VLC_UNUSED(demux); VLC_UNUSED(data); if(block->p_buffer[1] & 0x80) /* TS M-bit == discontinuity (RFC 2250, 2.1) */ { block->i_flags |= BLOCK_FLAG_DISCONTINUITY; } } /* Send a packet to a chained demuxer */ static void stream_decode (demux_t *demux, void *data, block_t *block) { if (data) vlc_demux_chained_Send(data, block); else block_Release (block); (void)demux; } static void *demux_init (demux_t *demux) { return stream_init (demux, demux->psz_demux); } /* * Static payload types handler */ /* PT=0 * PCMU: G.711 µ-law (RFC3551) */ static void *pcmu_init (demux_t *demux) { es_format_t fmt; es_format_Init (&fmt, AUDIO_ES, VLC_CODEC_MULAW); fmt.audio.i_rate = 8000; fmt.audio.i_physical_channels = AOUT_CHAN_CENTER; return codec_init (demux, &fmt); } /* PT=3 * GSM */ static void *gsm_init (demux_t *demux) { es_format_t fmt; es_format_Init (&fmt, AUDIO_ES, VLC_CODEC_GSM); fmt.audio.i_rate = 8000; fmt.audio.i_physical_channels = AOUT_CHAN_CENTER; return codec_init (demux, &fmt); } /* PT=8 * PCMA: G.711 A-law (RFC3551) */ static void *pcma_init (demux_t *demux) { es_format_t fmt; es_format_Init (&fmt, AUDIO_ES, VLC_CODEC_ALAW); fmt.audio.i_rate = 8000; fmt.audio.i_physical_channels = AOUT_CHAN_CENTER; return codec_init (demux, &fmt); } /* PT=10,11 * L16: 16-bits (network byte order) PCM */ static void *l16s_init (demux_t *demux) { es_format_t fmt; es_format_Init (&fmt, AUDIO_ES, VLC_CODEC_S16B); fmt.audio.i_rate = 44100; fmt.audio.i_physical_channels = AOUT_CHANS_STEREO; return codec_init (demux, &fmt); } static void *l16m_init (demux_t *demux) { es_format_t fmt; es_format_Init (&fmt, AUDIO_ES, VLC_CODEC_S16B); fmt.audio.i_rate = 44100; fmt.audio.i_physical_channels = AOUT_CHAN_CENTER; return codec_init (demux, &fmt); } /* PT=12 * QCELP */ static void *qcelp_init (demux_t *demux) { es_format_t fmt; es_format_Init (&fmt, AUDIO_ES, VLC_CODEC_QCELP); fmt.audio.i_rate = 8000; fmt.audio.i_physical_channels = AOUT_CHAN_CENTER; return codec_init (demux, &fmt); } /* PT=14 * MPA: MPEG Audio (RFC2250, §3.4) */ static void *mpa_init (demux_t *demux) { es_format_t fmt; es_format_Init (&fmt, AUDIO_ES, VLC_CODEC_MPGA); fmt.audio.i_physical_channels = AOUT_CHANS_STEREO; fmt.b_packetized = false; return codec_init (demux, &fmt); } static void mpa_decode (demux_t *demux, void *data, block_t *block) { if (block->i_buffer < 4) { block_Release (block); return; } block->i_buffer -= 4; /* 32-bits RTP/MPA header */ block->p_buffer += 4; codec_decode (demux, data, block); } /* PT=32 * MPV: MPEG Video (RFC2250, §3.5) */ static void *mpv_init (demux_t *demux) { es_format_t fmt; es_format_Init (&fmt, VIDEO_ES, VLC_CODEC_MPGV); fmt.b_packetized = false; return codec_init (demux, &fmt); } static void mpv_decode (demux_t *demux, void *data, block_t *block) { if (block->i_buffer < 4) { block_Release (block); return; } block->i_buffer -= 4; /* 32-bits RTP/MPV header */ block->p_buffer += 4; #if 0 if (block->p_buffer[-3] & 0x4) { /* MPEG2 Video extension header */ /* TODO: shouldn't we skip this too ? */ } #endif codec_decode (demux, data, block); } /* PT=33 * MP2: MPEG TS (RFC2250, §2) */ static void *ts_init (demux_t *demux) { char const* name = demux->psz_demux; if (*name == '\0' || !strcasecmp(name, "any")) name = NULL; return stream_init (demux, name ? name : "ts"); } /* Not using SDP, we need to guess the payload format used */ /* see http://www.iana.org/assignments/rtp-parameters */ void rtp_autodetect (demux_t *demux, rtp_session_t *session, const block_t *block) { uint8_t ptype = rtp_ptype (block); rtp_pt_t pt = { .init = NULL, .destroy = codec_destroy, .header = NULL, .decode = codec_decode, .frequency = 0, .number = ptype, }; /* Remember to keep this in sync with modules/services_discovery/sap.c */ switch (ptype) { case 0: msg_Dbg (demux, "detected G.711 mu-law"); pt.init = pcmu_init; pt.frequency = 8000; break; case 3: msg_Dbg (demux, "detected GSM"); pt.init = gsm_init; pt.frequency = 8000; break; case 8: msg_Dbg (demux, "detected G.711 A-law"); pt.init = pcma_init; pt.frequency = 8000; break; case 10: msg_Dbg (demux, "detected stereo PCM"); pt.init = l16s_init; pt.frequency = 44100; break; case 11: msg_Dbg (demux, "detected mono PCM"); pt.init = l16m_init; pt.frequency = 44100; break; case 12: msg_Dbg (demux, "detected QCELP"); pt.init = qcelp_init; pt.frequency = 8000; break; case 14: msg_Dbg (demux, "detected MPEG Audio"); pt.init = mpa_init; pt.decode = mpa_decode; pt.frequency = 90000; break; case 32: msg_Dbg (demux, "detected MPEG Video"); pt.init = mpv_init; pt.decode = mpv_decode; pt.frequency = 90000; break; case 33: msg_Dbg (demux, "detected MPEG2 TS"); pt.init = ts_init; pt.destroy = stream_destroy; pt.header = stream_header; pt.decode = stream_decode; pt.frequency = 90000; break; default: /* * If the rtp payload type is unknown then check demux if it is specified */ if (!strcmp(demux->psz_demux, "h264") || !strcmp(demux->psz_demux, "ts")) { msg_Dbg (demux, "dynamic payload format %s specified by demux", demux->psz_demux); pt.init = demux_init; pt.destroy = stream_destroy; pt.decode = stream_decode; pt.frequency = 90000; break; } if (ptype >= 96) { char *dynamic = var_InheritString(demux, "rtp-dynamic-pt"); if (dynamic == NULL) ; else if (!strcmp(dynamic, "theora")) { msg_Dbg (demux, "assuming Theora Encoded Video"); pt.init = theora_init; pt.destroy = xiph_destroy; pt.decode = xiph_decode; pt.frequency = 90000; free (dynamic); break; } else msg_Err (demux, "unknown dynamic payload format `%s' " "specified", dynamic); free (dynamic); } msg_Err (demux, "unspecified payload format (type %"PRIu8")", ptype); msg_Info (demux, "A valid SDP is needed to parse this RTP stream."); vlc_dialog_display_error (demux, N_("SDP required"), N_("A description in SDP format is required to receive the RTP " "stream. Note that rtp:// URIs cannot work with dynamic " "RTP payload format (%"PRIu8")."), ptype); return; } rtp_add_type (demux, session, &pt); } /* * Dynamic payload type handlers * Hmm, none implemented yet apart from Xiph ones. */