/* mediastreamer2 library - modular sound and video processing and streaming Copyright (C) 2006-2013 Belledonne Communications, Grenoble This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include "mediastreamer-config.h" #endif #include "ortp/port.h" #include "mediastreamer2/mediastream.h" #include "private.h" #include #ifndef MS_MINIMAL_MTU /*this is used for determining the minimum size of recv buffers for RTP packets Keep 1500 for maximum interoparibility*/ #define MS_MINIMAL_MTU 1500 #endif #if defined(_WIN32_WCE) time_t ms_time(time_t *t) { DWORD timemillis = GetTickCount(); if (timemillis > 0) { if (t != NULL) *t = timemillis / 1000; } return timemillis / 1000; } #endif static void disable_checksums(ortp_socket_t sock) { #if defined(DISABLE_CHECKSUMS) && defined(SO_NO_CHECK) int option = 1; if (setsockopt(sock, SOL_SOCKET, SO_NO_CHECK, &option, sizeof(option)) == -1) { ms_warning("Could not disable udp checksum: %s", strerror(errno)); } #endif } static int _ms_ticker_prio_from_env(const char *penv, MSTickerPrio *prio) { if (strcasecmp(penv, "NORMAL") == 0) { *prio = MS_TICKER_PRIO_NORMAL; return 0; } if (strcasecmp(penv, "HIGH") == 0) { *prio = MS_TICKER_PRIO_HIGH; return 0; } if (strcasecmp(penv, "REALTIME") == 0) { *prio = MS_TICKER_PRIO_REALTIME; return 0; } ms_error("Undefined priority %s", penv); return -1; } MSTickerPrio __ms_get_default_prio(bool_t is_video) { const char *penv = NULL; MSTickerPrio prio; if (is_video) { #ifndef MS2_WINDOWS_UNIVERSAL penv = getenv("MS_VIDEO_PRIO"); #endif if(penv && _ms_ticker_prio_from_env(penv, &prio) == 0) return prio; #ifdef __ios return MS_TICKER_PRIO_HIGH; #else return MS_TICKER_PRIO_NORMAL; #endif } else { #ifndef MS2_WINDOWS_UNIVERSAL penv = getenv("MS_AUDIO_PRIO"); #endif if (penv && _ms_ticker_prio_from_env(penv, &prio) == 0) return prio; return MS_TICKER_PRIO_HIGH; } } void media_stream_init(MediaStream *stream, MSFactory *factory) { stream->evd = ortp_ev_dispatcher_new(stream->sessions.rtp_session); stream->evq = ortp_ev_queue_new(); stream->factory = factory; /*the factory is used later to instanciate everything in mediastreamer2.*/ rtp_session_register_event_queue(stream->sessions.rtp_session, stream->evq); } RtpSession * ms_create_duplex_rtp_session(const char* local_ip, int loc_rtp_port, int loc_rtcp_port) { RtpSession *rtpr; rtpr = rtp_session_new(RTP_SESSION_SENDRECV); rtp_session_set_recv_buf_size(rtpr, MAX(ms_get_mtu() , MS_MINIMAL_MTU)); rtp_session_set_scheduling_mode(rtpr, 0); rtp_session_set_blocking_mode(rtpr, 0); rtp_session_enable_adaptive_jitter_compensation(rtpr, TRUE); rtp_session_set_symmetric_rtp(rtpr, TRUE); rtp_session_set_local_addr(rtpr, local_ip, loc_rtp_port, loc_rtcp_port); rtp_session_signal_connect(rtpr, "timestamp_jump", (RtpCallback)rtp_session_resync, NULL); rtp_session_signal_connect(rtpr, "ssrc_changed", (RtpCallback)rtp_session_resync, NULL); rtp_session_set_ssrc_changed_threshold(rtpr, 0); rtp_session_set_rtcp_report_interval(rtpr, 2500); /* At the beginning of the session send more reports. */ rtp_session_set_multicast_loopback(rtpr,TRUE); /*very useful, specially for testing purposes*/ disable_checksums(rtp_session_get_rtp_socket(rtpr)); return rtpr; } int media_stream_join_multicast_group(MediaStream *stream, const char *ip){ return rtp_session_join_multicast_group(stream->sessions.rtp_session,ip); } void media_stream_start_ticker(MediaStream *stream) { MSTickerParams params = {0}; char name[32] = {0}; if (stream->sessions.ticker) return; snprintf(name, sizeof(name) - 1, "%s MSTicker", media_stream_type_str(stream)); name[0] = toupper(name[0]); params.name = name; params.prio = __ms_get_default_prio((stream->type == MSVideo) ? TRUE : FALSE); stream->sessions.ticker = ms_ticker_new_with_params(¶ms); } const char * media_stream_type_str(MediaStream *stream) { return ms_format_type_to_string(stream->type); } void ms_media_stream_sessions_uninit(MSMediaStreamSessions *sessions){ if (sessions->srtp_context) { ms_srtp_context_delete(sessions->srtp_context); sessions->srtp_context=NULL; } if (sessions->rtp_session) { rtp_session_destroy(sessions->rtp_session); sessions->rtp_session=NULL; } if (sessions->zrtp_context != NULL) { ms_zrtp_context_destroy(sessions->zrtp_context); sessions->zrtp_context = NULL; } if (sessions->dtls_context != NULL) { ms_dtls_srtp_context_destroy(sessions->dtls_context); sessions->dtls_context = NULL; } if (sessions->ticker){ ms_ticker_destroy(sessions->ticker); sessions->ticker=NULL; } } void media_stream_free(MediaStream *stream) { if (stream->sessions.rtp_session != NULL) rtp_session_unregister_event_queue(stream->sessions.rtp_session, stream->evq); if (stream->evq != NULL) ortp_ev_queue_destroy(stream->evq); if (stream->evd != NULL) ortp_ev_dispatcher_destroy(stream->evd); if (stream->owns_sessions) ms_media_stream_sessions_uninit(&stream->sessions); if (stream->rc != NULL) ms_bitrate_controller_destroy(stream->rc); if (stream->rtpsend != NULL) ms_filter_destroy(stream->rtpsend); if (stream->rtprecv != NULL) ms_filter_destroy(stream->rtprecv); if (stream->encoder != NULL) ms_filter_destroy(stream->encoder); if (stream->decoder != NULL) ms_filter_destroy(stream->decoder); if (stream->voidsink != NULL) ms_filter_destroy(stream->voidsink); if (stream->qi) ms_quality_indicator_destroy(stream->qi); } bool_t media_stream_started(MediaStream *stream) { return stream->start_time != 0; } void media_stream_set_rtcp_information(MediaStream *stream, const char *cname, const char *tool) { if (stream->sessions.rtp_session != NULL) { rtp_session_set_source_description(stream->sessions.rtp_session, cname, NULL, NULL, NULL, NULL, tool, NULL); } } void media_stream_get_local_rtp_stats(MediaStream *stream, rtp_stats_t *lstats) { if (stream->sessions.rtp_session) { const rtp_stats_t *stats = rtp_session_get_stats(stream->sessions.rtp_session); memcpy(lstats, stats, sizeof(*stats)); } else memset(lstats, 0, sizeof(rtp_stats_t)); } int media_stream_set_dscp(MediaStream *stream, int dscp) { ms_message("Setting DSCP to %i for %s stream.", dscp, media_stream_type_str(stream)); return rtp_session_set_dscp(stream->sessions.rtp_session, dscp); } void media_stream_enable_adaptive_bitrate_control(MediaStream *stream, bool_t enabled) { stream->rc_enable = enabled; } void media_stream_set_adaptive_bitrate_algorithm(MediaStream *stream, MSQosAnalyzerAlgorithm algorithm) { stream->rc_algorithm = algorithm; } void media_stream_enable_adaptive_jittcomp(MediaStream *stream, bool_t enabled) { rtp_session_enable_adaptive_jitter_compensation(stream->sessions.rtp_session, enabled); } void media_stream_enable_dtls(MediaStream *stream, MSDtlsSrtpParams *params){ if (stream->sessions.dtls_context==NULL) { ms_message("Start DTLS media stream context in stream session [%p]", &(stream->sessions)); stream->sessions.dtls_context=ms_dtls_srtp_context_new(&(stream->sessions), params); } } bool_t media_stream_dtls_supported(void){ return ms_dtls_srtp_available(); } /*deprecated*/ bool_t media_stream_enable_srtp(MediaStream *stream, MSCryptoSuite suite, const char *snd_key, const char *rcv_key) { return ms_media_stream_sessions_set_srtp_recv_key_b64(&stream->sessions,suite,rcv_key)==0 && ms_media_stream_sessions_set_srtp_send_key_b64(&stream->sessions,suite,snd_key)==0; } const MSQualityIndicator *media_stream_get_quality_indicator(MediaStream *stream){ return stream->qi; } bool_t ms_is_ipv6(const char *remote) { bool_t ret = FALSE; struct addrinfo hints, *res0; int err; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_NUMERICHOST; err = getaddrinfo(remote,"8000", &hints, &res0); if (err != 0) { ms_warning("ms_is_ipv6(%s): %s", remote, gai_strerror(err)); return FALSE; } ret = (res0->ai_addr->sa_family == AF_INET6); freeaddrinfo(res0); return ret; } bool_t ms_is_multicast_addr(const struct sockaddr *addr) { switch (addr->sa_family) { case AF_INET: return IN_MULTICAST(ntohl(((struct sockaddr_in *) addr)->sin_addr.s_addr)); case AF_INET6: return IN6_IS_ADDR_MULTICAST(&(((struct sockaddr_in6 *) addr)->sin6_addr)); default: return FALSE; } } bool_t ms_is_multicast(const char *address) { bool_t ret = FALSE; struct addrinfo hints, *res0; int err; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_NUMERICHOST; err = getaddrinfo(address,"8000", &hints, &res0); if (err != 0) { ms_warning("ms_is_multicast(%s): %s", address, gai_strerror(err)); return FALSE; } ret = ms_is_multicast_addr(res0->ai_addr); freeaddrinfo(res0); return ret; } static void media_stream_process_rtcp(MediaStream *stream, mblk_t *m, time_t curtime){ stream->last_packet_time=curtime; ms_message("%s stream [%p]: receiving RTCP %s%s",media_stream_type_str(stream),stream,(rtcp_is_SR(m)?"SR":""),(rtcp_is_RR(m)?"RR":"")); do{ if (stream->rc_enable && stream->rc) ms_bitrate_controller_process_rtcp(stream->rc,m); if (stream->qi) ms_quality_indicator_update_from_feedback(stream->qi,m); stream->process_rtcp(stream,m); }while(rtcp_next_packet(m)); } void media_stream_iterate(MediaStream *stream){ time_t curtime=ms_time(NULL); if (stream->ice_check_list) ice_check_list_process(stream->ice_check_list,stream->sessions.rtp_session); /*we choose to update the quality indicator as much as possible, since local statistics can be computed realtime. */ if (stream->state==MSStreamStarted){ if (stream->is_beginning && (curtime-stream->start_time>15)){ rtp_session_set_rtcp_report_interval(stream->sessions.rtp_session,5000); stream->is_beginning=FALSE; } if (stream->qi && curtime>stream->last_iterate_time) ms_quality_indicator_update_local(stream->qi); } stream->last_iterate_time=curtime; if (stream->rc) ms_bitrate_controller_update(stream->rc); if (stream->evd) { ortp_ev_dispatcher_iterate(stream->evd); } if (stream->evq){ OrtpEvent *ev=NULL; while ((ev=ortp_ev_queue_get(stream->evq))!=NULL){ OrtpEventType evt=ortp_event_get_type(ev); if (evt==ORTP_EVENT_RTCP_PACKET_RECEIVED){ mblk_t *m=ortp_event_get_data(ev)->packet; media_stream_process_rtcp(stream,m,curtime); }else if (evt==ORTP_EVENT_RTCP_PACKET_EMITTED){ ms_message("%s_stream_iterate[%p], local statistics available:" "\n\tLocal current jitter buffer size: %5.1fms", media_stream_type_str(stream), stream, rtp_session_get_jitter_stats(stream->sessions.rtp_session)->jitter_buffer_size_ms); } else if (evt==ORTP_EVENT_STUN_PACKET_RECEIVED){ if (stream->ice_check_list) { ice_handle_stun_packet(stream->ice_check_list,stream->sessions.rtp_session,ortp_event_get_data(ev)); } else if (rtp_session_get_symmetric_rtp(stream->sessions.rtp_session)){ /*try to know if we can trust stun packets for symetric rtp*/ rtp_stats_t stats; media_stream_get_local_rtp_stats(stream, &stats); if (stats.packet_recv == 0 && !ms_is_multicast_addr((const struct sockaddr *)&stream->sessions.rtp_session->rtp.gs.rem_addr)) { memcpy(&stream->sessions.rtp_session->rtp.gs.rem_addr,&ortp_event_get_data(ev)->source_addr,ortp_event_get_data(ev)->source_addrlen); ms_message("stun packet received but no rtp yet for stream [%p], switching rtp destination address",stream); } } } else if ((evt == ORTP_EVENT_ZRTP_ENCRYPTION_CHANGED) || (evt == ORTP_EVENT_DTLS_ENCRYPTION_CHANGED)) { ms_message("%s_stream_iterate[%p]: is %s ",media_stream_type_str(stream) , stream, media_stream_secured(stream) ? "encrypted" : "not encrypted"); } ortp_event_destroy(ev); } } } bool_t media_stream_alive(MediaStream *ms, int timeout){ const rtp_stats_t *stats; if (ms->state!=MSStreamStarted){ return TRUE; } stats=rtp_session_get_stats(ms->sessions.rtp_session); if (stats->recv!=0){ if (stats->recv!=ms->last_packet_count){ ms->last_packet_count=stats->recv; ms->last_packet_time=ms_time(NULL); } } if (ms_time(NULL)-ms->last_packet_time>timeout){ /* more than timeout seconds of inactivity*/ return FALSE; } return TRUE; } float media_stream_get_quality_rating(MediaStream *stream){ if (stream->qi){ return ms_quality_indicator_get_rating(stream->qi); } return -1; } float media_stream_get_average_quality_rating(MediaStream *stream){ if (stream->qi){ return ms_quality_indicator_get_average_rating(stream->qi); } return -1; } float media_stream_get_lq_quality_rating(MediaStream *stream) { if (stream->qi) { return ms_quality_indicator_get_lq_rating(stream->qi); } return -1; } float media_stream_get_average_lq_quality_rating(MediaStream *stream) { if (stream->qi) { return ms_quality_indicator_get_average_lq_rating(stream->qi); } return -1; } int media_stream_set_target_network_bitrate(MediaStream *stream,int target_bitrate) { stream->target_bitrate=target_bitrate; return 0; } int media_stream_get_target_network_bitrate(const MediaStream *stream) { return stream->target_bitrate; } float media_stream_get_up_bw(const MediaStream *stream) { return rtp_session_get_rtp_send_bandwidth(stream->sessions.rtp_session); } float media_stream_get_down_bw(const MediaStream *stream) { return rtp_session_get_rtp_recv_bandwidth(stream->sessions.rtp_session); } float media_stream_get_rtcp_up_bw(const MediaStream *stream) { return rtp_session_get_rtcp_send_bandwidth(stream->sessions.rtp_session); } float media_stream_get_rtcp_down_bw(const MediaStream *stream) { return rtp_session_get_rtcp_recv_bandwidth(stream->sessions.rtp_session); } void media_stream_reclaim_sessions(MediaStream *stream, MSMediaStreamSessions *sessions){ memcpy(sessions,&stream->sessions, sizeof(MSMediaStreamSessions)); stream->owns_sessions=FALSE; } bool_t media_stream_secured (const MediaStream *stream) { if (stream->state != MSStreamStarted) return FALSE; switch (stream->type) { case MSAudio: case MSText: /*fixme need also audio stream direction to be more precise*/ return ms_media_stream_sessions_secured(&stream->sessions, MediaStreamSendRecv); case MSVideo:{ VideoStream *vs = (VideoStream*)stream; return ms_media_stream_sessions_secured(&stream->sessions, vs->dir); } } return FALSE; } bool_t media_stream_avpf_enabled(const MediaStream *stream) { return rtp_session_avpf_enabled(stream->sessions.rtp_session); } uint16_t media_stream_get_avpf_rr_interval(const MediaStream *stream) { return rtp_session_get_avpf_rr_interval(stream->sessions.rtp_session); } MSStreamState media_stream_get_state(const MediaStream *stream) { return stream->state; } RtpSession * media_stream_get_rtp_session(const MediaStream *stream) { return stream->sessions.rtp_session; } #define keywordcmp(key,b) strncmp(key,b,sizeof(key)) /* see http://www.iana.org/assignments/sdp-security-descriptions/sdp-security-descriptions.xhtml#sdp-security-descriptions-3 */ MSCryptoSuite ms_crypto_suite_build_from_name_params(const MSCryptoSuiteNameParams *descrption){ const char *name=descrption->name, *parameters=descrption->params; if (keywordcmp ( "AES_CM_128_HMAC_SHA1_80",name ) == 0 ){ if (parameters && strstr(parameters,"UNENCRYPTED_SRTP")) return MS_NO_CIPHER_SHA1_80; else if (parameters && strstr(parameters,"UNAUTHENTICATED_SRTP")) return MS_AES_128_NO_AUTH; else return MS_AES_128_SHA1_80; }else if ( keywordcmp ( "AES_CM_128_HMAC_SHA1_32",name ) == 0 ){ if (parameters && strstr(parameters,"UNENCRYPTED_SRTP")) goto error; if (parameters && strstr(parameters,"UNAUTHENTICATED_SRTP")) return MS_AES_128_NO_AUTH; else return MS_AES_128_SHA1_32; }else if ( keywordcmp ( "AES_CM_256_HMAC_SHA1_32",name ) == 0 ){ if (parameters && strstr(parameters,"UNENCRYPTED_SRTP")) goto error; if (parameters && strstr(parameters,"UNAUTHENTICATED_SRTP")) goto error; return MS_AES_256_SHA1_32; }else if ( keywordcmp ( "AES_CM_256_HMAC_SHA1_80",name ) == 0 ){ if (parameters && strstr(parameters,"UNENCRYPTED_SRTP")) goto error; if (parameters && strstr(parameters,"UNAUTHENTICATED_SRTP")) goto error; return MS_AES_256_SHA1_80; } error: ms_error("Unsupported crypto suite '%s' with parameters '%s'",name, parameters ? parameters : ""); return MS_CRYPTO_SUITE_INVALID; } int ms_crypto_suite_to_name_params(MSCryptoSuite cs, MSCryptoSuiteNameParams *params ){ params->name=NULL; params->params=NULL; switch(cs){ case MS_CRYPTO_SUITE_INVALID: break; case MS_AES_128_SHA1_80: params->name= "AES_CM_128_HMAC_SHA1_80"; break; case MS_AES_128_SHA1_32: params->name="AES_CM_128_HMAC_SHA1_32"; break; case MS_AES_128_NO_AUTH: params->name="AES_CM_128_HMAC_SHA1_80"; params->params="UNAUTHENTICATED_SRTP"; break; case MS_NO_CIPHER_SHA1_80: params->name="AES_CM_128_HMAC_SHA1_80"; params->params="UNENCRYPTED_SRTP UNENCRYPTED_SRTCP"; break; case MS_AES_256_SHA1_80: params->name="AES_CM_256_HMAC_SHA1_80"; break; case MS_AES_256_SHA1_32: params->name="AES_CM_256_HMAC_SHA1_32"; break; } if (params->name==NULL) return -1; return 0; } OrtpEvDispatcher* media_stream_get_event_dispatcher(const MediaStream *stream) { return stream->evd; } const char *ms_resource_type_to_string(MSResourceType type){ switch(type){ case MSResourceDefault: return "MSResourceDefault"; case MSResourceInvalid: return "MSResourceInvalid"; case MSResourceCamera: return "MSResourceCamera"; case MSResourceFile: return "MSResourceFile"; case MSResourceRtp: return "MSResourceRtp"; case MSResourceSoundcard: return "MSResourceSoundcard"; } return "INVALID"; } bool_t ms_media_resource_is_consistent(const MSMediaResource *r){ switch(r->type){ case MSResourceCamera: case MSResourceRtp: case MSResourceSoundcard: if (r->resource_arg == NULL){ ms_error("No resource argument specified for resource type %s", ms_resource_type_to_string(r->type)); return FALSE; } return TRUE; break; case MSResourceFile: /*setting up file player/recorder without specifying the file to play immediately is allowed*/ case MSResourceDefault: return TRUE; case MSResourceInvalid: ms_error("Invalid resource type specified"); return FALSE; } ms_error("Unsupported media resource type [%i]", (int)r->type); return FALSE; } bool_t ms_media_stream_io_is_consistent(const MSMediaStreamIO *io){ return ms_media_resource_is_consistent(&io->input) && ms_media_resource_is_consistent(&io->output); } /*stubs*/ #ifndef VIDEO_ENABLED void video_stream_open_player(VideoStream *stream, MSFilter *sink){ } void video_stream_close_player(VideoStream *stream){ } const char *video_stream_get_default_video_renderer(void){ return NULL; } MSWebCamDesc *ms_mire_webcam_desc_get(void){ return NULL; } #endif