|
From: | Vincent Nguyen |
Subject: | [Linphone-developers] Question vbr / bitrate file misc.c |
Date: | Fri, 26 Jun 2015 20:02:58 +0200 |
User-agent: | Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20100101 Thunderbird/31.7.0 |
Hi I got a question regarding the misc.c file handling bitrate at call setup. For instance : If I pick the audio codec SPEEX32 (wide band 32Khz and vbr bitrate up to 44) When max bandwidth are set to zero (default value) and if a video stream h264 is on, then call is set at 24 bps upstream, and remains at this value. If I remove the video stream, or if I set a maxbandwidth up at 1500 ou 2000 then the bitrate goes up, butmax out at 38.4 is this a bug ? or due to what is in bold below ? I really would like to setp wide band calls using the speex32 or speex16. /* linphone Copyright (C) 2000 Simon MORLAT (address@hidden) 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. */ #include "private.h" #include "lpconfig.h" #include "mediastreamer2/mediastream.h" #include <stdlib.h> #include <stdio.h> #ifdef HAVE_SIGHANDLER_T #include <signal.h> #endif /*HAVE_SIGHANDLER_T*/ #include <string.h> #if !defined(_WIN32_WCE) #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #if _MSC_VER #include <io.h> #else #include <unistd.h> #endif #include <fcntl.h> #endif /*_WIN32_WCE*/ #undef snprintf #include <mediastreamer2/stun.h> #ifdef HAVE_GETIFADDRS #include <net/if.h> #include <ifaddrs.h> #endif #include <math.h> #if _MSC_VER #define snprintf _snprintf #define popen _popen #define pclose _pclose #endif #define UDP_HDR_SZ 8 #define RTP_HDR_SZ 12 #define IP4_HDR_SZ 20 /*20 is the minimum, but there may be some options*/ bool_t linphone_core_payload_type_enabled(LinphoneCore *lc, const LinphonePayloadType *pt){ if (ms_list_find(lc->codecs_conf.audio_codecs, (PayloadType*) pt) || ms_list_find(lc->codecs_conf.video_codecs, (PayloadType*)pt)){ return payload_type_enabled(pt); } ms_error("Getting enablement status of codec not in audio or video list of PayloadType !"); return FALSE; } bool_t linphone_core_payload_type_is_vbr(LinphoneCore *lc, const LinphonePayloadType *pt){ if (pt->type==PAYLOAD_VIDEO) return TRUE; return !!(pt->flags & PAYLOAD_TYPE_IS_VBR); } int linphone_core_enable_payload_type(LinphoneCore *lc, LinphonePayloadType *pt, bool_t enabled){ if (ms_list_find(lc->codecs_conf.audio_codecs,pt) || ms_list_find(lc->codecs_conf.video_codecs,pt)){ payload_type_set_enable(pt,enabled); _linphone_core_codec_config_write(lc); linphone_core_update_allocated_audio_bandwidth(lc); return 0; } ms_error("Enabling codec not in audio or video list of PayloadType !"); return -1; } int linphone_core_get_payload_type_number(LinphoneCore *lc, const PayloadType *pt){ return payload_type_get_number(pt); } void linphone_core_set_payload_type_number(LinphoneCore *lc, PayloadType *pt, int number){ payload_type_set_number(pt,number); } const char *linphone_core_get_payload_type_description(LinphoneCore *lc, PayloadType *pt){ if (ms_filter_codec_supported(pt->mime_type)){ MSFilterDesc *desc=ms_filter_get_encoder(pt->mime_type); #ifdef ENABLE_NLS return dgettext("mediastreamer",desc->text); #else return desc->text; #endif } return NULL; } void linphone_core_set_payload_type_bitrate(LinphoneCore *lc, LinphonePayloadType *pt, int bitrate){ if (ms_list_find(lc->codecs_conf.audio_codecs, (PayloadType*) pt) || ms_list_find(lc->codecs_conf.video_codecs, (PayloadType*)pt)){ if (pt->type==PAYLOAD_VIDEO || pt->flags & PAYLOAD_TYPE_IS_VBR){ pt->normal_bitrate=bitrate*1000; pt->flags|=PAYLOAD_TYPE_BITRATE_OVERRIDE; linphone_core_update_allocated_audio_bandwidth(lc); }else{ ms_error("Cannot set an explicit bitrate for codec %s/%i, because it is not VBR.",pt->mime_type,pt->clock_rate); return; } } else { ms_error("linphone_core_set_payload_type_bitrate() payload type not in audio or video list !"); } } /* *((codec-birate*ptime/8) + RTP header + UDP header + IP header)*8/ptime; *ptime=1/npacket */ static double get_audio_payload_bandwidth_from_codec_bitrate(const PayloadType *pt){ double npacket=50; double packet_size; int bitrate; if (strcmp(payload_type_get_mime(&payload_type_aaceld_44k), payload_type_get_mime(pt))==0) { /*special case of aac 44K because ptime= 10ms*/ npacket=100; }else if (strcmp(payload_type_get_mime(&payload_type_ilbc), payload_type_get_mime(pt))==0) { npacket=1000/30.0; } bitrate=pt->normal_bitrate; packet_size= (((double)bitrate)/(npacket*8))+UDP_HDR_SZ+RTP_HDR_SZ+IP4_HDR_SZ; return packet_size*8.0*npacket; } typedef struct vbr_codec_bitrate{ int max_avail_bitrate; int min_rate; int recomended_bitrate; }vbr_codec_bitrate_t; static vbr_codec_bitrate_t defauls_vbr[]={ //{ 100, 44100, 100 }, { 64, 44100, 50 }, { 64, 16000, 40 }, { 32, 16000, 32 }, { 32, 8000, 32 }, { 0 , 8000, 24 }, { 0 , 0, 0 } }; static int lookup_vbr_typical_bitrate(int maxbw, int clock_rate){ vbr_codec_bitrate_t *it; if (maxbw<=0) maxbw=defauls_vbr[0].max_avail_bitrate; for(it=defauls_vbr;it->min_rate!=0;it++){ if (maxbw>=it->max_avail_bitrate && clock_rate>=it->min_rate) return it->recomended_bitrate; } ms_error("lookup_vbr_typical_bitrate(): should not happen."); return 32; } static int get_audio_payload_bandwidth(LinphoneCore *lc, const PayloadType *pt, int maxbw){ if (linphone_core_payload_type_is_vbr(lc,pt)){ if (pt->flags & PAYLOAD_TYPE_BITRATE_OVERRIDE){ ms_debug("PayloadType %s/%i has bitrate override",pt->mime_type,pt->clock_rate); return pt->normal_bitrate/1000; } return lookup_vbr_typical_bitrate(maxbw,pt->clock_rate); }else return (int)ceil(get_audio_payload_bandwidth_from_codec_bitrate(pt)/1000.0);/*rounding codec bandwidth should be avoid, specially for AMR*/ } int linphone_core_get_payload_type_bitrate(LinphoneCore *lc, const LinphonePayloadType *pt){ int maxbw=get_min_bandwidth(linphone_core_get_download_bandwidth(lc), linphone_core_get_upload_bandwidth(lc)); if (pt->type==PAYLOAD_AUDIO_CONTINUOUS || pt->type==PAYLOAD_AUDIO_PACKETIZED){ return get_audio_payload_bandwidth(lc,pt,maxbw); }else if (pt->type==PAYLOAD_VIDEO){ int video_bw; if (maxbw<=0) { video_bw=1500; /*default bitrate for video stream when no bandwidth limit is set, around 1.5 Mbit/s*/ }else{ video_bw=get_remaining_bandwidth_for_video(maxbw,lc->audio_bw); } return video_bw; } return 0; } void linphone_core_update_allocated_audio_bandwidth_in_call(LinphoneCall *call, const PayloadType *pt, int maxbw){ call->audio_bw=get_audio_payload_bandwidth(call->core,pt,maxbw); ms_message("Audio bandwidth for this call is %i",call->audio_bw); } void linphone_core_update_allocated_audio_bandwidth(LinphoneCore *lc){ const MSList *elem; int maxbw=get_min_bandwidth(linphone_core_get_download_bandwidth(lc), linphone_core_get_upload_bandwidth(lc)); int max_codec_bitrate=0; for(elem=linphone_core_get_audio_codecs(lc);elem!=NULL;elem=elem->next){ PayloadType *pt=(PayloadType*)elem->data; if (payload_type_enabled(pt)){ int pt_bitrate=get_audio_payload_bandwidth(lc,pt,maxbw); if (max_codec_bitrate==0) { max_codec_bitrate=pt_bitrate; }else if (max_codec_bitrate<pt_bitrate){ max_codec_bitrate=pt_bitrate; } } } if (max_codec_bitrate) { lc->audio_bw=max_codec_bitrate; } } bool_t linphone_core_is_payload_type_usable_for_bandwidth(LinphoneCore *lc, const PayloadType *pt, int bandwidth_limit) { double codec_band; bool_t ret=FALSE; switch (pt->type){ case PAYLOAD_AUDIO_CONTINUOUS: case PAYLOAD_AUDIO_PACKETIZED: codec_band=get_audio_payload_bandwidth(lc,pt,bandwidth_limit); ret=bandwidth_is_greater(bandwidth_limit*1000,codec_band); //ms_message("Payload %s: %g",pt->mime_type,codec_band); break; case PAYLOAD_VIDEO: if (bandwidth_limit!=0) {/* infinite (-1) or strictly positive*/ ret=TRUE; } else ret=FALSE; break; } return ret; } /* return TRUE if codec can be used with bandwidth, FALSE else*/ bool_t linphone_core_check_payload_type_usability(LinphoneCore *lc, const PayloadType *pt){ bool_t ret=linphone_core_is_payload_type_usable_for_bandwidth(lc, pt, linphone_core_get_payload_type_bitrate(lc,pt)); if ((pt->type==PAYLOAD_AUDIO_CONTINUOUS || pt->type==PAYLOAD_AUDIO_PACKETIZED) && lc->sound_conf.capt_sndcard && !(ms_snd_card_get_capabilities(lc->sound_conf.capt_sndcard) & MS_SND_CARD_CAP_BUILTIN_ECHO_CANCELLER) && linphone_core_echo_cancellation_enabled(lc) && (pt->clock_rate!=16000 && pt->clock_rate!=8000) && strcasecmp(pt->mime_type,"opus")!=0 && ms_filter_lookup_by_name("MSWebRTCAEC")!=NULL){ ms_warning("Payload type %s/%i cannot be used because software echo cancellation is required but is unable to operate at this rate.", pt->mime_type,pt->clock_rate); ret=FALSE; } return ret; } bool_t lp_spawn_command_line_sync(const char *command, char **result,int *command_ret){ #if !defined(_WIN32_WCE) FILE *f=popen(command,"r"); if (f!=NULL){ int err; *result=ms_malloc(4096); err=fread(*result,1,4096-1,f); if (err<0){ ms_warning("Error reading command output:%s",strerror(errno)); ms_free(result); return FALSE; } (*result)[err]=0; err=pclose(f); if (command_ret!=NULL) *command_ret=err; return TRUE; } #endif /*_WIN32_WCE*/ return FALSE; } static ortp_socket_t create_socket(int local_port){ struct sockaddr_in laddr; ortp_socket_t sock; int optval; sock=socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP); if (sock<0) { ms_error("Fail to create socket"); return -1; } memset (&laddr,0,sizeof(laddr)); laddr.sin_family=AF_INET; laddr.sin_addr.s_addr=INADDR_ANY; laddr.sin_port=htons(local_port); if (bind(sock,(struct sockaddr*)&laddr,sizeof(laddr))<0){ ms_error("Bind socket to 0.0.0.0:%i failed: %s",local_port,getSocketError()); close_socket(sock); return -1; } optval=1; if (setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, (SOCKET_OPTION_VALUE)&optval, sizeof (optval))<0){ ms_warning("Fail to set SO_REUSEADDR"); } set_non_blocking_socket(sock); return sock; } static int sendStunRequest(int sock, const struct sockaddr *server, socklen_t addrlen, int id, bool_t changeAddr){ char buf[STUN_MAX_MESSAGE_SIZE]; int len = STUN_MAX_MESSAGE_SIZE; StunAtrString username; StunAtrString password; StunMessage req; int err; memset(&req, 0, sizeof(StunMessage)); memset(&username,0,sizeof(username)); memset(&password,0,sizeof(password)); stunBuildReqSimple( &req, &username, changeAddr , changeAddr , id); len = stunEncodeMessage( &req, buf, len, &password); if (len<=0){ ms_error("Fail to encode stun message."); return -1; } err=sendto(sock,buf,len,0,server,addrlen); if (err<0){ ms_error("sendto failed: %s",strerror(errno)); return -1; } return 0; } int linphone_parse_host_port(const char *input, char *host, size_t hostlen, int *port){ char tmphost[NI_MAXHOST]={0}; char *p1, *p2; if ((sscanf(input, "[%64[^]]]:%d", tmphost, port) == 2) || (sscanf(input, "[%64[^]]]", tmphost) == 1)) { } else { p1 = strchr(input, ':'); p2 = strrchr(input, ':'); if (p1 && p2 && (p1 != p2)) {/* an ipv6 address without port*/ strncpy(tmphost, input, sizeof(tmphost) - 1); } else if (sscanf(input, "%[^:]:%d", tmphost, port) != 2) { /*no port*/ strncpy(tmphost, input, sizeof(tmphost) - 1); } } strncpy(host,tmphost,hostlen); return 0; } int parse_hostname_to_addr(const char *server, struct sockaddr_storage *ss, socklen_t *socklen, int default_port){ struct addrinfo hints,*res=NULL; char port[6]; char host[NI_MAXHOST]; int port_int=default_port; int ret; linphone_parse_host_port(server,host,sizeof(host),&port_int); snprintf(port, sizeof(port), "%d", port_int); memset(&hints,0,sizeof(hints)); hints.ai_family=AF_UNSPEC; hints.ai_socktype=SOCK_DGRAM; hints.ai_protocol=IPPROTO_UDP; ret=getaddrinfo(host,port,&hints,&res); if (ret!=0){ ms_error("getaddrinfo() failed for %s:%s : %s",host,port,gai_strerror(ret)); return -1; } if (!res) return -1; memcpy(ss,res->ai_addr,res->ai_addrlen); *socklen=res->ai_addrlen; freeaddrinfo(res); return 0; } static int recvStunResponse(ortp_socket_t sock, char *ipaddr, int *port, int *id){ char buf[STUN_MAX_MESSAGE_SIZE]; int len = STUN_MAX_MESSAGE_SIZE; StunMessage resp; len=recv(sock,buf,len,0); if (len>0){ struct in_addr ia; stunParseMessage(buf,len, &resp ); *id=resp.msgHdr.tr_id.octet[0]; if (resp.hasXorMappedAddress){ *port = resp.xorMappedAddress.ipv4.port; ia.s_addr=htonl(resp.xorMappedAddress.ipv4.addr); }else if (resp.hasMappedAddress){ *port = resp.mappedAddress.ipv4.port; ia.s_addr=htonl(resp.mappedAddress.ipv4.addr); }else return -1; strncpy(ipaddr,inet_ntoa(ia),LINPHONE_IPADDR_SIZE); } return len; } /* this functions runs a simple stun test and return the number of milliseconds to complete the tests, or -1 if the test were failed.*/ int linphone_core_run_stun_tests(LinphoneCore *lc, LinphoneCall *call){ const char *server=linphone_core_get_stun_server(lc); StunCandidate *ac=&call->ac; StunCandidate *vc=&call->vc; if (lc->sip_conf.ipv6_enabled){ ms_warning("stun support is not implemented for ipv6"); return -1; } if (call->media_ports[0].rtp_port==-1){ ms_warning("Stun-only support not available for system random port"); return -1; } if (server!=NULL){ const struct addrinfo *ai=linphone_core_get_stun_server_addrinfo(lc); ortp_socket_t sock1=-1, sock2=-1; int loops=0; bool_t video_enabled=linphone_core_video_enabled(lc); bool_t got_audio,got_video; bool_t cone_audio=FALSE,cone_video=FALSE; struct timeval init,cur; double elapsed; int ret=0; if (ai==NULL){ ms_error("Could not obtain stun server addrinfo."); return -1; } linphone_core_notify_display_status(lc,_("Stun lookup in progress...")); /*create the two audio and video RTP sockets, and send STUN message to our stun server */ sock1=create_socket(call->media_ports[0].rtp_port); if (sock1==-1) return -1; if (video_enabled){ sock2=create_socket(call->media_ports[1].rtp_port); if (sock2==-1) return -1; } got_audio=FALSE; got_video=FALSE; ortp_gettimeofday(&init,NULL); do{ int id; if (loops%20==0){ ms_message("Sending stun requests..."); sendStunRequest(sock1,ai->ai_addr,ai->ai_addrlen,11,TRUE); sendStunRequest(sock1,ai->ai_addr,ai->ai_addrlen,1,FALSE); if (sock2!=-1){ sendStunRequest(sock2,ai->ai_addr,ai->ai_addrlen,22,TRUE); sendStunRequest(sock2,ai->ai_addr,ai->ai_addrlen,2,FALSE); } } ms_usleep(10000); if (recvStunResponse(sock1,ac->addr, &ac->port,&id)>0){ ms_message("STUN test result: local audio port maps to %s:%i", ac->addr, ac->port); if (id==11) cone_audio=TRUE; got_audio=TRUE; } if (recvStunResponse(sock2,vc->addr, &vc->port,&id)>0){ ms_message("STUN test result: local video port maps to %s:%i", vc->addr, vc->port); if (id==22) cone_video=TRUE; got_video=TRUE; } ortp_gettimeofday(&cur,NULL); elapsed=((cur.tv_sec-init.tv_sec)*1000.0) + ((cur.tv_usec-init.tv_usec)/1000.0); if (elapsed>2000) { ms_message("Stun responses timeout, going ahead."); ret=-1; break; } loops++; }while(!(got_audio && (got_video||sock2==-1) ) ); if (ret==0) ret=(int)elapsed; if (!got_audio){ ms_error("No stun server response for audio port."); }else{ if (!cone_audio) { ms_message("NAT is symmetric for audio port"); } } if (sock2!=-1){ if (!got_video){ ms_error("No stun server response for video port."); }else{ if (!cone_video) { ms_message("NAT is symmetric for video port."); } } } close_socket(sock1); if (sock2!=-1) close_socket(sock2); return ret; } return -1; } int linphone_core_get_edge_bw(LinphoneCore *lc){ int edge_bw=lp_config_get_int(lc->config,"net","edge_bw",20); return edge_bw; } int linphone_core_get_edge_ptime(LinphoneCore *lc){ int edge_ptime=lp_config_get_int(lc->config,"net","edge_ptime",100); return edge_ptime; } void linphone_core_adapt_to_network(LinphoneCore *lc, int ping_time_ms, LinphoneCallParams *params){ int threshold; if (ping_time_ms>0 && lp_config_get_int(lc->config,"net","activate_edge_workarounds",0)==1){ ms_message("Stun server ping time is %i ms",ping_time_ms); threshold=lp_config_get_int(lc->config,"net","edge_ping_time",500); if (ping_time_ms>threshold){ /* we might be in a 2G network*/ params->low_bandwidth=TRUE; }/*else use default settings */ } if (params->low_bandwidth){ params->up_bw=params->down_bw=linphone_core_get_edge_bw(lc); params->up_ptime=params->down_ptime=linphone_core_get_edge_ptime(lc); params->has_video=FALSE; } } static void stun_server_resolved(LinphoneCore *lc, const char *name, struct addrinfo *addrinfo){ if (lc->net_conf.stun_addrinfo){ belle_sip_freeaddrinfo(lc->net_conf.stun_addrinfo); lc->net_conf.stun_addrinfo=NULL; } if (addrinfo){ ms_message("Stun server resolution successful."); }else{ ms_warning("Stun server resolution failed."); } lc->net_conf.stun_addrinfo=addrinfo; lc->net_conf.stun_res=NULL; } void linphone_core_resolve_stun_server(LinphoneCore *lc){ /* * WARNING: stun server resolution only done in IPv4. * TODO: use IPv6 resolution if linphone_core_ipv6_enabled()==TRUE and use V4Mapped addresses for ICE gathering. */ const char *server=lc->net_conf.stun_server; if (lc->sal && server && !lc->net_conf.stun_res){ char host[NI_MAXHOST]; int port=3478; linphone_parse_host_port(server,host,sizeof(host),&port); lc->net_conf.stun_res=sal_resolve_a(lc->sal,host,port,AF_INET,(SalResolverCallback)stun_server_resolved,lc); } } /* * This function returns the addrinfo representation of the stun server address. * It is critical not to block for a long time if it can't be resolved, otherwise this stucks the main thread when making a call. * On the contrary, a fully asynchronous call initiation is complex to develop. * The compromise is then: * - have a cache of the stun server addrinfo * - this cached value is returned when it is non-null * - an asynchronous resolution is asked each time this function is called to ensure frequent refreshes of the cached value. * - if no cached value exists, block for a short time; this case must be unprobable because the resolution will be asked each time the stun server value is * changed. **/ const struct addrinfo *linphone_core_get_stun_server_addrinfo(LinphoneCore *lc){ const char *server=linphone_core_get_stun_server(lc); if (server){ int wait_ms=0; int wait_limit=1000; linphone_core_resolve_stun_server(lc); while (!lc->net_conf.stun_addrinfo && lc->net_conf.stun_res!=NULL && wait_ms<wait_limit){ sal_iterate(lc->sal); ms_usleep(50000); wait_ms+=50; } } return lc->net_conf.stun_addrinfo; } int linphone_core_gather_ice_candidates(LinphoneCore *lc, LinphoneCall *call) { char local_addr[64]; const struct addrinfo *ai; IceCheckList *audio_check_list; IceCheckList *video_check_list; const char *server = linphone_core_get_stun_server(lc); if ((server == NULL) || (call->ice_session == NULL)) return -1; audio_check_list = ice_session_check_list(call->ice_session, 0); video_check_list = ice_session_check_list(call->ice_session, 1); if (audio_check_list == NULL) return -1; if (call->af==AF_INET6){ ms_warning("Ice gathering is not implemented for ipv6"); return -1; } ai=linphone_core_get_stun_server_addrinfo(lc); if (ai==NULL){ ms_warning("Fail to resolve STUN server for ICE gathering."); return -1; } linphone_core_notify_display_status(lc, _("ICE local candidates gathering in progress...")); /* Gather local host candidates. */ if (linphone_core_get_local_ip_for(AF_INET, NULL, local_addr) < 0) { ms_error("Fail to get local ip"); return -1; } if ((ice_check_list_state(audio_check_list) != ICL_Completed) && (ice_check_list_candidates_gathered(audio_check_list) == FALSE)) { ice_add_local_candidate(audio_check_list, "host", local_addr, call->media_ports[0].rtp_port, 1, NULL); ice_add_local_candidate(audio_check_list, "host", local_addr, call->media_ports[0].rtcp_port, 2, NULL); call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateInProgress; } if (linphone_core_video_enabled(lc) && (video_check_list != NULL) && (ice_check_list_state(video_check_list) != ICL_Completed) && (ice_check_list_candidates_gathered(video_check_list) == FALSE)) { ice_add_local_candidate(video_check_list, "host", local_addr, call->media_ports[1].rtp_port, 1, NULL); ice_add_local_candidate(video_check_list, "host", local_addr, call->media_ports[1].rtcp_port, 2, NULL); call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateInProgress; } ms_message("ICE: gathering candidate from [%s]",server); /* Gather local srflx candidates. */ ice_session_gather_candidates(call->ice_session, ai->ai_addr, ai->ai_addrlen); return 0; } const char *linphone_ice_state_to_string(LinphoneIceState state){ switch(state){ case LinphoneIceStateFailed: return "IceStateFailed"; case LinphoneIceStateHostConnection: return "IceStateHostConnection"; case LinphoneIceStateInProgress: return "IceStateInProgress"; case LinphoneIceStateNotActivated: return "IceStateNotActivated"; case LinphoneIceStateReflexiveConnection: return "IceStateReflexiveConnection"; case LinphoneIceStateRelayConnection: return "IceStateRelayConnection"; } return "invalid"; } void linphone_core_update_ice_state_in_call_stats(LinphoneCall *call) { IceCheckList *audio_check_list; IceCheckList *video_check_list; IceSessionState session_state; if (call->ice_session == NULL) return; audio_check_list = ice_session_check_list(call->ice_session, 0); video_check_list = ice_session_check_list(call->ice_session, 1); if (audio_check_list == NULL) return; session_state = ice_session_state(call->ice_session); if ((session_state == IS_Completed) || ((session_state == IS_Failed) && (ice_session_has_completed_check_list(call->ice_session) == TRUE))) { if (ice_check_list_state(audio_check_list) == ICL_Completed) { switch (ice_check_list_selected_valid_candidate_type(audio_check_list)) { case ICT_HostCandidate: call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateHostConnection; break; case ICT_ServerReflexiveCandidate: case ICT_PeerReflexiveCandidate: call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateReflexiveConnection; break; case ICT_RelayedCandidate: call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateRelayConnection; break; } } else { call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateFailed; } if (call->params->has_video && (video_check_list != NULL)) { if (ice_check_list_state(video_check_list) == ICL_Completed) { switch (ice_check_list_selected_valid_candidate_type(video_check_list)) { case ICT_HostCandidate: call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateHostConnection; break; case ICT_ServerReflexiveCandidate: case ICT_PeerReflexiveCandidate: call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateReflexiveConnection; break; case ICT_RelayedCandidate: call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateRelayConnection; break; } } else { call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateFailed; } }else call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateNotActivated; } else if (session_state == IS_Running) { call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateInProgress; if (call->params->has_video && (video_check_list != NULL)) { call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateInProgress; } } else { call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateFailed; if (call->params->has_video && (video_check_list != NULL)) { call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateFailed; } } ms_message("Call [%p] New ICE state: audio: [%s] video: [%s]", call, linphone_ice_state_to_string(call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state),linphone_ice_state_to_string(call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state)); } void _update_local_media_description_from_ice(SalMediaDescription *desc, IceSession *session) { const char *rtp_addr, *rtcp_addr; IceSessionState session_state = ice_session_state(session); int nb_candidates; int i, j; bool_t result; if (session_state == IS_Completed) { desc->ice_completed = TRUE; result = ice_check_list_selected_valid_local_candidate(ice_session_check_list(session, 0), &rtp_addr, NULL, NULL, NULL); if (result == TRUE) { strncpy(desc->addr, rtp_addr, sizeof(desc->addr)); } else { ms_warning("If ICE has completed successfully, rtp_addr should be set!"); } } else { desc->ice_completed = FALSE; } strncpy(desc->ice_pwd, ice_session_local_pwd(session), sizeof(desc->ice_pwd)); strncpy(desc->ice_ufrag, ice_session_local_ufrag(session), sizeof(desc->ice_ufrag)); for (i = 0; i < desc->nb_streams; i++) { SalStreamDescription *stream = &desc->streams[i]; IceCheckList *cl = ice_session_check_list(session, i); nb_candidates = 0; if (!sal_stream_description_active(stream) || (cl == NULL)) continue; if (ice_check_list_state(cl) == ICL_Completed) { stream->ice_completed = TRUE; result = ice_check_list_selected_valid_local_candidate(ice_session_check_list(session, i), &rtp_addr, &stream->rtp_port, &rtcp_addr, &stream->rtcp_port); } else { stream->ice_completed = FALSE; result = ice_check_list_default_local_candidate(ice_session_check_list(session, i), &rtp_addr, &stream->rtp_port, &rtcp_addr, &stream->rtcp_port); } if (result == TRUE) { strncpy(stream->rtp_addr, rtp_addr, sizeof(stream->rtp_addr)); strncpy(stream->rtcp_addr, rtcp_addr, sizeof(stream->rtcp_addr)); } else { memset(stream->rtp_addr, 0, sizeof(stream->rtp_addr)); memset(stream->rtcp_addr, 0, sizeof(stream->rtcp_addr)); } if ((strlen(ice_check_list_local_pwd(cl)) != strlen(desc->ice_pwd)) || (strcmp(ice_check_list_local_pwd(cl), desc->ice_pwd))) strncpy(stream->ice_pwd, ice_check_list_local_pwd(cl), sizeof(stream->ice_pwd)); else memset(stream->ice_pwd, 0, sizeof(stream->ice_pwd)); if ((strlen(ice_check_list_local_ufrag(cl)) != strlen(desc->ice_ufrag)) || (strcmp(ice_check_list_local_ufrag(cl), desc->ice_ufrag))) strncpy(stream->ice_ufrag, ice_check_list_local_ufrag(cl), sizeof(stream->ice_ufrag)); else memset(stream->ice_pwd, 0, sizeof(stream->ice_pwd)); stream->ice_mismatch = ice_check_list_is_mismatch(cl); if ((ice_check_list_state(cl) == ICL_Running) || (ice_check_list_state(cl) == ICL_Completed)) { memset(stream->ice_candidates, 0, sizeof(stream->ice_candidates)); for (j = 0; j < MIN(ms_list_size(cl->local_candidates), SAL_MEDIA_DESCRIPTION_MAX_ICE_CANDIDATES); j++) { SalIceCandidate *sal_candidate = &stream->ice_candidates[nb_candidates]; IceCandidate *ice_candidate = ms_list_nth_data(cl->local_candidates, j); const char *default_addr = NULL; int default_port = 0; if (ice_candidate->componentID == 1) { default_addr = stream->rtp_addr; default_port = stream->rtp_port; } else if (ice_candidate->componentID == 2) { default_addr = stream->rtcp_addr; default_port = stream->rtcp_port; } else continue; if (default_addr[0] == '\0') default_addr = desc->addr; /* Only include the candidates matching the default destination for each component of the stream if the state is Completed as specified in RFC5245 section 9.1.2.2. */ if ((ice_check_list_state(cl) == ICL_Completed) && !((ice_candidate->taddr.port == default_port) && (strlen(ice_candidate->taddr.ip) == strlen(default_addr)) && (strcmp(ice_candidate->taddr.ip, default_addr) == 0))) continue; strncpy(sal_candidate->foundation, ice_candidate->foundation, sizeof(sal_candidate->foundation)); sal_candidate->componentID = ice_candidate->componentID; sal_candidate->priority = ice_candidate->priority; strncpy(sal_candidate->type, ice_candidate_type(ice_candidate), sizeof(sal_candidate->type)); strncpy(sal_candidate->addr, ice_candidate->taddr.ip, sizeof(sal_candidate->addr)); sal_candidate->port = ice_candidate->taddr.port; if ((ice_candidate->base != NULL) && (ice_candidate->base != ice_candidate)) { strncpy(sal_candidate->raddr, ice_candidate->base->taddr.ip, sizeof(sal_candidate->raddr)); sal_candidate->rport = ice_candidate->base->taddr.port; } nb_candidates++; } } if ((ice_check_list_state(cl) == ICL_Completed) && (ice_session_role(session) == IR_Controlling)) { int rtp_port, rtcp_port; memset(stream->ice_remote_candidates, 0, sizeof(stream->ice_remote_candidates)); if (ice_check_list_selected_valid_remote_candidate(cl, &rtp_addr, &rtp_port, &rtcp_addr, &rtcp_port) == TRUE) { strncpy(stream->ice_remote_candidates[0].addr, rtp_addr, sizeof(stream->ice_remote_candidates[0].addr)); stream->ice_remote_candidates[0].port = rtp_port; strncpy(stream->ice_remote_candidates[1].addr, rtcp_addr, sizeof(stream->ice_remote_candidates[1].addr)); stream->ice_remote_candidates[1].port = rtcp_port; } else { ms_error("ice: Selected valid remote candidates should be present if the check list is in the Completed state"); } } else { for (j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_REMOTE_CANDIDATES; j++) { stream->ice_remote_candidates[j].addr[0] = '\0'; stream->ice_remote_candidates[j].port = 0; } } } } static void get_default_addr_and_port(uint16_t componentID, const SalMediaDescription *md, const SalStreamDescription *stream, const char **addr, int *port) { if (componentID == 1) { *addr = stream->rtp_addr; *port = stream->rtp_port; } else if (componentID == 2) { *addr = stream->rtcp_addr; *port = stream->rtcp_port; } else return; if ((*addr)[0] == '\0') *addr = md->addr; } static void clear_ice_check_list(LinphoneCall *call, IceCheckList *removed){ if (call->audiostream && call->audiostream->ms.ice_check_list==removed) call->audiostream->ms.ice_check_list=NULL; if (call->videostream && call->videostream->ms.ice_check_list==removed) call->videostream->ms.ice_check_list=NULL; } void linphone_call_update_ice_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) { bool_t ice_restarted = FALSE; bool_t ice_params_found=FALSE; if ((md->ice_pwd[0] != '\0') && (md->ice_ufrag[0] != '\0')) { ice_params_found=TRUE; } else { int i; for (i = 0; i < md->nb_streams; i++) { const SalStreamDescription *stream = &md->streams[i]; IceCheckList *cl = ice_session_check_list(call->ice_session, i); if (cl) { if ((stream->ice_pwd[0] != '\0') && (stream->ice_ufrag[0] != '\0')) { ice_params_found=TRUE; } else { ice_params_found=FALSE; break; } } } } if (ice_params_found) { int i, j; /* Check for ICE restart and set remote credentials. */ if ((strcmp(md->addr, "0.0.0.0") == 0) || (strcmp(md->addr, "::0") == 0)) { ice_session_restart(call->ice_session); ice_restarted = TRUE; } else { for (i = 0; i < md->nb_streams; i++) { const SalStreamDescription *stream = &md->streams[i]; IceCheckList *cl = ice_session_check_list(call->ice_session, i); if (cl && (strcmp(stream->rtp_addr, "0.0.0.0") == 0)) { ice_session_restart(call->ice_session); ice_restarted = TRUE; break; } } } if ((ice_session_remote_ufrag(call->ice_session) == NULL) && (ice_session_remote_pwd(call->ice_session) == NULL)) { ice_session_set_remote_credentials(call->ice_session, md->ice_ufrag, md->ice_pwd); } else if (ice_session_remote_credentials_changed(call->ice_session, md->ice_ufrag, md->ice_pwd)) { if (ice_restarted == FALSE) { ice_session_restart(call->ice_session); ice_restarted = TRUE; } ice_session_set_remote_credentials(call->ice_session, md->ice_ufrag, md->ice_pwd); } for (i = 0; i < md->nb_streams; i++) { const SalStreamDescription *stream = &md->streams[i]; IceCheckList *cl = ice_session_check_list(call->ice_session, i); if (cl && (stream->ice_pwd[0] != '\0') && (stream->ice_ufrag[0] != '\0')) { if (ice_check_list_remote_credentials_changed(cl, stream->ice_ufrag, stream->ice_pwd)) { if (ice_restarted == FALSE && ice_check_list_get_remote_ufrag(cl) && ice_check_list_get_remote_pwd(cl)) { /* restart onlu if remote ufrag/paswd was already set*/ ice_session_restart(call->ice_session); ice_restarted = TRUE; } ice_check_list_set_remote_credentials(cl, stream->ice_ufrag, stream->ice_pwd); break; } } } /* Create ICE check lists if needed and parse ICE attributes. */ for (i = 0; i < md->nb_streams; i++) { const SalStreamDescription *stream = &md->streams[i]; IceCheckList *cl = ice_session_check_list(call->ice_session, i); /* if ((cl == NULL) && (i < md->n_active_streams)) { cl = ice_check_list_new(); ice_session_add_check_list(call->ice_session, cl); switch (stream->type) { case SalAudio: if (call->audiostream != NULL) call->audiostream->ms.ice_check_list = cl; break; case SalVideo: if (call->videostream != NULL) call->videostream->ms.ice_check_list = cl; break; default: break; } } */ if (cl==NULL) continue; if (stream->ice_mismatch == TRUE) { ice_check_list_set_state(cl, ICL_Failed); } else if (stream->rtp_port == 0) { ice_session_remove_check_list(call->ice_session, cl); clear_ice_check_list(call,cl); } else { if ((stream->ice_pwd[0] != '\0') && (stream->ice_ufrag[0] != '\0')) ice_check_list_set_remote_credentials(cl, stream->ice_ufrag, stream->ice_pwd); for (j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_CANDIDATES; j++) { const SalIceCandidate *candidate = &stream->ice_candidates[j]; bool_t default_candidate = FALSE; const char *addr = NULL; int port = 0; if (candidate->addr[0] == '\0') break; if ((candidate->componentID == 0) || (candidate->componentID > 2)) continue; get_default_addr_and_port(candidate->componentID, md, stream, &addr, &port); if (addr && (candidate->port == port) && (strlen(candidate->addr) == strlen(addr)) && (strcmp(candidate->addr, addr) == 0)) default_candidate = TRUE; ice_add_remote_candidate(cl, candidate->type, candidate->addr, candidate->port, candidate->componentID, candidate->priority, candidate->foundation, default_candidate); } if (ice_restarted == FALSE) { bool_t losing_pairs_added = FALSE; for (j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_REMOTE_CANDIDATES; j++) { const SalIceRemoteCandidate *candidate = &stream->ice_remote_candidates[j]; const char *addr = NULL; int port = 0; int componentID = j + 1; if (candidate->addr[0] == '\0') break; get_default_addr_and_port(componentID, md, stream, &addr, &port); if (j == 0) { /* If we receive a re-invite and we finished ICE processing on our side, use the candidates given by the remote. */ ice_check_list_unselect_valid_pairs(cl); } ice_add_losing_pair(cl, j + 1, candidate->addr, candidate->port, addr, port); losing_pairs_added = TRUE; } if (losing_pairs_added == TRUE) ice_check_list_check_completed(cl); } } } for (i = 0; i < md->nb_streams; i++) { IceCheckList * cl = ice_session_check_list(call->ice_session, i); if (!sal_stream_description_active(&md->streams[i]) && (cl != NULL)) { ice_session_remove_check_list_from_idx(call->ice_session, i); clear_ice_check_list(call, cl); } } ice_session_check_mismatch(call->ice_session); } else { /* Response from remote does not contain mandatory ICE attributes, delete the session. */ linphone_call_delete_ice_session(call); return; } if (ice_session_nb_check_lists(call->ice_session) == 0) { linphone_call_delete_ice_session(call); } } bool_t linphone_core_media_description_contains_video_stream(const SalMediaDescription *md){ int i; for (i = 0; md && i < md->nb_streams; i++) { if (md->streams[i].type == SalVideo && md->streams[i].rtp_port!=0) return TRUE; } return FALSE; } unsigned int linphone_core_get_audio_features(LinphoneCore *lc){ unsigned int ret=0; const char *features=lp_config_get_string(lc->config,"sound","features",NULL); if (features){ char tmp[256]={0}; char name[256]; char *p,*n; strncpy(tmp,features,sizeof(tmp)-1); for(p=tmp;*p!='\0';p++){ if (*p==' ') continue; n=strchr(p,'|'); if (n) *n='\0'; sscanf(p,"%s",name); ms_message("Found audio feature %s",name); if (strcasecmp(name,"PLC")==0) ret|=AUDIO_STREAM_FEATURE_PLC; else if (strcasecmp(name,"EC")==0) ret|=AUDIO_STREAM_FEATURE_EC; else if (strcasecmp(name,"EQUALIZER")==0) ret|=AUDIO_STREAM_FEATURE_EQUALIZER; else if (strcasecmp(name,"VOL_SND")==0) ret|=AUDIO_STREAM_FEATURE_VOL_SND; else if (strcasecmp(name,"VOL_RCV")==0) ret|=AUDIO_STREAM_FEATURE_VOL_RCV; else if (strcasecmp(name,"DTMF")==0) ret|=AUDIO_STREAM_FEATURE_DTMF; else if (strcasecmp(name,"DTMF_ECHO")==0) ret|=AUDIO_STREAM_FEATURE_DTMF_ECHO; else if (strcasecmp(name,"MIXED_RECORDING")==0) ret|=AUDIO_STREAM_FEATURE_MIXED_RECORDING; else if (strcasecmp(name,"LOCAL_PLAYING")==0) ret|=AUDIO_STREAM_FEATURE_LOCAL_PLAYING; else if (strcasecmp(name,"REMOTE_PLAYING")==0) ret|=AUDIO_STREAM_FEATURE_REMOTE_PLAYING; else if (strcasecmp(name,"ALL")==0) ret|=AUDIO_STREAM_FEATURE_ALL; else if (strcasecmp(name,"NONE")==0) ret=0; else ms_error("Unsupported audio feature %s requested in config file.",name); if (!n) break; p=n; } }else ret=AUDIO_STREAM_FEATURE_ALL; if (ret==AUDIO_STREAM_FEATURE_ALL){ /*since call recording is specified before creation of the stream in linphonecore, * it will be requested on demand. It is not necessary to include it all the time*/ ret&=~AUDIO_STREAM_FEATURE_MIXED_RECORDING; } return ret; } bool_t linphone_core_tone_indications_enabled(LinphoneCore*lc){ return lp_config_get_int(lc->config,"sound","tone_indications",1); } #ifdef HAVE_GETIFADDRS #include <ifaddrs.h> static int get_local_ip_with_getifaddrs(int type, char *address, int size){ struct ifaddrs *ifp; struct ifaddrs *ifpstart; char retaddr[LINPHONE_IPADDR_SIZE]={0}; bool_t found=FALSE; if (getifaddrs(&ifpstart) < 0) { return -1; } #ifndef __linux #define UP_FLAG IFF_UP /* interface is up */ #else #define UP_FLAG IFF_RUNNING /* resources allocated */ #endif for (ifp = ifpstart; ifp != NULL; ifp = ifp->ifa_next) { if (ifp->ifa_addr && ifp->ifa_addr->sa_family == type && (ifp->ifa_flags & UP_FLAG) && !(ifp->ifa_flags & IFF_LOOPBACK)) { if(getnameinfo(ifp->ifa_addr, (type == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in), retaddr, size, NULL, 0, NI_NUMERICHOST) == 0) { if (strchr(retaddr, '%') == NULL) { /*avoid ipv6 link-local addresses */ /*ms_message("getifaddrs() found %s",address);*/ found=TRUE; break; } } } } freeifaddrs(ifpstart); if (found) strncpy(address,retaddr,size); return found; } #endif static int get_local_ip_for_with_connect(int type, const char *dest, char *result){ int err,tmp; struct addrinfo hints; struct addrinfo *res=NULL; struct sockaddr_storage addr; struct sockaddr *p_addr=(struct sockaddr*)&addr; ortp_socket_t sock; socklen_t s; memset(&hints,0,sizeof(hints)); hints.ai_family=type; hints.ai_socktype=SOCK_DGRAM; /*hints.ai_flags=AI_NUMERICHOST|AI_CANONNAME;*/ err=getaddrinfo(dest,"5060",&hints,&res); if (err!=0){ ms_error("getaddrinfo() error for %s : %s",dest, gai_strerror(err)); return -1; } if (res==NULL){ ms_error("bug: getaddrinfo returned nothing."); return -1; } sock=socket(res->ai_family,SOCK_DGRAM,0); tmp=1; err=setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(SOCKET_OPTION_VALUE)&tmp,sizeof(int)); if (err<0){ ms_warning("Error in setsockopt: %s",strerror(errno)); } err=connect(sock,res->ai_addr,res->ai_addrlen); if (err<0) { /*the network isn't reachable*/ if (getSocketErrorCode()!=ENETUNREACH) ms_error("Error in connect: %s",strerror(errno)); freeaddrinfo(res); close_socket(sock); return -1; } freeaddrinfo(res); res=NULL; s=sizeof(addr); err=getsockname(sock,(struct sockaddr*)&addr,&s); if (err!=0) { ms_error("Error in getsockname: %s",strerror(errno)); close_socket(sock); return -1; } if (p_addr->sa_family==AF_INET){ struct sockaddr_in *p_sin=(struct sockaddr_in*)p_addr; if (p_sin->sin_addr.s_addr==0){ close_socket(sock); return -1; } } err=getnameinfo((struct sockaddr *)&addr,s,result,LINPHONE_IPADDR_SIZE,NULL,0,NI_NUMERICHOST); if (err!=0){ ms_error("getnameinfo error: %s",strerror(errno)); } /*avoid ipv6 link-local addresses*/ if (p_addr->sa_family==AF_INET6 && strchr(result,'%')!=NULL){ strcpy(result,"::1"); close_socket(sock); return -1; } close_socket(sock); return 0; } int linphone_core_get_local_ip_for(int type, const char *dest, char *result){ int err; #ifdef HAVE_GETIFADDRS int found_ifs; #endif strcpy(result,type==AF_INET ? "127.0.0.1" : "::1"); if (dest==NULL){ if (type==AF_INET) dest="87.98.157.38"; /*a public IP address*/ else dest="2a00:1450:8002::68"; } err=get_local_ip_for_with_connect(type,dest,result); if (err==0) return 0; /* if the connect method failed, which happens when no default route is set, * try to find 'the' running interface with getifaddrs*/ #ifdef HAVE_GETIFADDRS /*we use getifaddrs for lookup of default interface */ found_ifs=get_local_ip_with_getifaddrs(type,result,LINPHONE_IPADDR_SIZE); if (found_ifs==1){ return 0; }else if (found_ifs<=0){ /*absolutely no network on this machine */ return -1; } #endif return 0; } void linphone_core_get_local_ip(LinphoneCore *lc, int af, const char *dest, char *result) { if (af == AF_UNSPEC) { if (linphone_core_ipv6_enabled(lc)) { bool_t has_ipv6 = linphone_core_get_local_ip_for(AF_INET6, dest, result) == 0; if (strcmp(result, "::1") != 0) return; /*this machine has real ipv6 connectivity*/ if ((linphone_core_get_local_ip_for(AF_INET, dest, result) == 0) && (strcmp(result, "127.0.0.1") != 0)) return; /*this machine has only ipv4 connectivity*/ if (has_ipv6) { /*this machine has only local loopback for both ipv4 and ipv6, so prefer ipv6*/ strncpy(result, "::1", LINPHONE_IPADDR_SIZE); return; } } /*in all other cases use IPv4*/ af = AF_INET; } linphone_core_get_local_ip_for(af, dest, result); } SalReason linphone_reason_to_sal(LinphoneReason reason){ switch(reason){ case LinphoneReasonNone: return SalReasonNone; case LinphoneReasonNoResponse: return SalReasonRequestTimeout; case LinphoneReasonForbidden: return SalReasonForbidden; case LinphoneReasonDeclined: return SalReasonDeclined; case LinphoneReasonNotFound: return SalReasonNotFound; case LinphoneReasonTemporarilyUnavailable: return SalReasonTemporarilyUnavailable; case LinphoneReasonBusy: return SalReasonBusy; case LinphoneReasonNotAcceptable: return SalReasonNotAcceptable; case LinphoneReasonIOError: return SalReasonServiceUnavailable; case LinphoneReasonDoNotDisturb: return SalReasonDoNotDisturb; case LinphoneReasonUnauthorized: return SalReasonUnauthorized; case LinphoneReasonUnsupportedContent: return SalReasonUnsupportedContent; case LinphoneReasonNoMatch: return SalReasonNoMatch; case LinphoneReasonMovedPermanently: return SalReasonMovedPermanently; case LinphoneReasonGone: return SalReasonGone; case LinphoneReasonAddressIncomplete: return SalReasonAddressIncomplete; case LinphoneReasonNotImplemented: return SalReasonNotImplemented; case LinphoneReasonBadGateway: return SalReasonBadGateway; case LinphoneReasonServerTimeout: return SalReasonServerTimeout; case LinphoneReasonNotAnswered: return SalReasonRequestTimeout; case LinphoneReasonUnknown: return SalReasonUnknown; } return SalReasonUnknown; } LinphoneReason linphone_reason_from_sal(SalReason r){ LinphoneReason ret=LinphoneReasonNone; switch(r){ case SalReasonNone: ret=LinphoneReasonNone; break; case SalReasonIOError: ret=LinphoneReasonIOError; break; case SalReasonUnknown: ret=LinphoneReasonUnknown; break; case SalReasonBusy: ret=LinphoneReasonBusy; break; case SalReasonDeclined: ret=LinphoneReasonDeclined; break; case SalReasonDoNotDisturb: ret=LinphoneReasonDoNotDisturb; break; case SalReasonForbidden: ret=LinphoneReasonBadCredentials; break; case SalReasonNotAcceptable: ret=LinphoneReasonNotAcceptable; break; case SalReasonNotFound: ret=LinphoneReasonNotFound; break; case SalReasonRedirect: ret=LinphoneReasonNone; break; case SalReasonTemporarilyUnavailable: ret=LinphoneReasonTemporarilyUnavailable; break; case SalReasonServiceUnavailable: ret=LinphoneReasonIOError; break; case SalReasonRequestPending: ret=LinphoneReasonNone; break; case SalReasonUnauthorized: ret=LinphoneReasonUnauthorized; break; case SalReasonUnsupportedContent: ret=LinphoneReasonUnsupportedContent; break; case SalReasonNoMatch: ret=LinphoneReasonNoMatch; break; case SalReasonRequestTimeout: ret=LinphoneReasonNotAnswered; break; case SalReasonMovedPermanently: ret=LinphoneReasonMovedPermanently; break; case SalReasonGone: ret=LinphoneReasonGone; break; case SalReasonAddressIncomplete: ret=LinphoneReasonAddressIncomplete; break; case SalReasonNotImplemented: ret=LinphoneReasonNotImplemented; break; case SalReasonBadGateway: ret=LinphoneReasonBadGateway; break; case SalReasonServerTimeout: ret=LinphoneReasonServerTimeout; break; } return ret; } /** * Get reason code from the error info. * @param ei the error info. * @return a #LinphoneReason * @ingroup misc **/ LinphoneReason linphone_error_info_get_reason(const LinphoneErrorInfo *ei){ const SalErrorInfo *sei=(const SalErrorInfo*)ei; return linphone_reason_from_sal(sei->reason); } /** * Get textual phrase from the error info. * This is the text that is provided by the peer in the protocol (SIP). * @param ei the error info. * @return the error phrase * @ingroup misc **/ const char *linphone_error_info_get_phrase(const LinphoneErrorInfo *ei){ const SalErrorInfo *sei=(const SalErrorInfo*)ei; return sei->status_string; } /** * Provides additional information regarding the failure. * With SIP protocol, the "Reason" and "Warning" headers are returned. * @param ei the error info. * @return more details about the failure. * @ingroup misc **/ const char *linphone_error_info_get_details(const LinphoneErrorInfo *ei){ const SalErrorInfo *sei=(const SalErrorInfo*)ei; return sei->warnings; } /** * Get the status code from the low level protocol (ex a SIP status code). * @param ei the error info. * @return the status code. * @ingroup misc **/ int linphone_error_info_get_protocol_code(const LinphoneErrorInfo *ei){ const SalErrorInfo *sei=(const SalErrorInfo*)ei; return sei->protocol_code; } /** * Set the name of the mediastreamer2 filter to be used for rendering video. * This is for advanced users of the library, mainly to workaround hardware/driver bugs. * @ingroup media_parameters **/ void linphone_core_set_video_display_filter(LinphoneCore *lc, const char *filter_name){ lp_config_set_string(lc->config,"video","displaytype",filter_name); } /** * Get the name of the mediastreamer2 filter used for rendering video. * @ingroup media_parameters **/ const char *linphone_core_get_video_display_filter(LinphoneCore *lc){ return lp_config_get_string(lc->config,"video","displaytype",NULL); } /** * Queue a task into the main loop. The data pointer must remain valid until the task is completed. * task_fun must return BELLE_SIP_STOP when job is finished. **/ void linphone_core_queue_task(LinphoneCore *lc, belle_sip_source_func_t task_fun, void *data, const char *task_description){ belle_sip_source_t *s=sal_create_timer(lc->sal,task_fun,data, 20, task_description); belle_sip_object_unref(s); } static int get_unique_transport(LinphoneCore *lc, LinphoneTransportType *type, int *port){ LCSipTransports tp; linphone_core_get_sip_transports(lc,&tp); if (tp.tcp_port==0 && tp.tls_port==0 && tp.udp_port!=0){ *type=LinphoneTransportUdp; *port=tp.udp_port; return 0; }else if (tp.tcp_port==0 && tp.udp_port==0 && tp.tls_port!=0){ *type=LinphoneTransportTls; *port=tp.tls_port; return 0; }else if (tp.tcp_port!=0 && tp.udp_port==0 && tp.tls_port==0){ *type=LinphoneTransportTcp; *port=tp.tcp_port; return 0; } return -1; } static void linphone_core_migrate_proxy_config(LinphoneCore *lc, LinphoneTransportType type){ const MSList *elem; for(elem=linphone_core_get_proxy_config_list(lc);elem!=NULL;elem=elem->next){ LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)elem->data; const char *proxy=linphone_proxy_config_get_addr(cfg); const char *route=linphone_proxy_config_get_route(cfg); LinphoneAddress *proxy_addr=linphone_address_new(proxy); LinphoneAddress *route_addr=NULL; char *tmp; if (route) route_addr=linphone_address_new(route); if (proxy_addr){ linphone_address_set_transport(proxy_addr,type); tmp=linphone_address_as_string(proxy_addr); linphone_proxy_config_set_server_addr(cfg,tmp); ms_free(tmp); linphone_address_destroy(proxy_addr); } if (route_addr){ linphone_address_set_transport(route_addr,type); tmp=linphone_address_as_string(route_addr); linphone_proxy_config_set_route(cfg,tmp); ms_free(tmp); linphone_address_destroy(route_addr); } } } /** * Migrate configuration so that all SIP transports are enabled. * Versions of linphone < 3.7 did not support using multiple SIP transport simultaneously. * This function helps application to migrate the configuration so that all transports are enabled. * Existing proxy configuration are added a transport parameter so that they continue using the unique transport that was set previously. * This function must be used just after creating the core, before any call to linphone_core_iterate() * @param lc the linphone core * @return 1 if migration was done, 0 if not done because unnecessary or already done, -1 in case of error. * @ingroup initializing **/ int linphone_core_migrate_to_multi_transport(LinphoneCore *lc){ if (!lp_config_get_int(lc->config,"sip","multi_transport_migration_done",0)){ LinphoneTransportType tpt; int port; if (get_unique_transport(lc,&tpt,&port)==0){ LCSipTransports newtp={0}; if (lp_config_get_int(lc->config,"sip","sip_random_port",0)) port=-1; ms_message("Core is using a single SIP transport, migrating proxy config and enabling multi-transport."); linphone_core_migrate_proxy_config(lc,tpt); newtp.udp_port=port; newtp.tcp_port=port; newtp.tls_port=LC_SIP_TRANSPORT_RANDOM; lp_config_set_string(lc->config, "sip","sip_random_port",NULL); /*remove*/ linphone_core_set_sip_transports(lc,&newtp); } lp_config_set_int(lc->config,"sip","multi_transport_migration_done",1); return 1; } return 0; } LinphoneToneDescription * linphone_tone_description_new(LinphoneReason reason, LinphoneToneID id, const char *audiofile){ LinphoneToneDescription *obj=ms_new0(LinphoneToneDescription,1); obj->reason=reason; obj->toneid=id; obj->audiofile=audiofile ? ms_strdup(audiofile) : NULL; return obj; } void linphone_tone_description_destroy(LinphoneToneDescription *obj){ if (obj->audiofile) ms_free(obj->audiofile); ms_free(obj); } LinphoneToneDescription *linphone_core_get_call_error_tone(const LinphoneCore *lc, LinphoneReason reason){ const MSList *elem; for (elem=lc->tones;elem!=NULL;elem=elem->next){ LinphoneToneDescription *tone=(LinphoneToneDescription*)elem->data; if (tone->reason==reason) return tone; } return NULL; } const char *linphone_core_get_tone_file(const LinphoneCore *lc, LinphoneToneID id){ const MSList *elem; for (elem=lc->tones;elem!=NULL;elem=elem->next){ LinphoneToneDescription *tone=(LinphoneToneDescription*)elem->data; if (tone->toneid==id && tone->reason==LinphoneReasonNone && tone->audiofile!=NULL) return tone->audiofile; } return NULL; } void _linphone_core_set_tone(LinphoneCore *lc, LinphoneReason reason, LinphoneToneID id, const char *audiofile){ LinphoneToneDescription *tone=linphone_core_get_call_error_tone(lc,reason); if (tone){ lc->tones=ms_list_remove(lc->tones,tone); linphone_tone_description_destroy(tone); } tone=linphone_tone_description_new(reason,id,audiofile); lc->tones=ms_list_append(lc->tones,tone); } /** * Assign an audio file to be played locally upon call failure, for a given reason. * @param lc the core * @param reason the #LinphoneReason representing the failure error code. * @param audiofile a wav file to be played when such call failure happens. * @ingroup misc **/ void linphone_core_set_call_error_tone(LinphoneCore *lc, LinphoneReason reason, const char *audiofile){ _linphone_core_set_tone(lc,reason,LinphoneToneUndefined, audiofile); } /** * Assign an audio file to be played as a specific tone id. * This function typically allows to customize telephony tones per country. * @param lc the core * @param id the tone id * @param audiofile a wav file to be played. **/ void linphone_core_set_tone(LinphoneCore *lc, LinphoneToneID id, const char *audiofile){ _linphone_core_set_tone(lc, LinphoneReasonNone, id, audiofile); } const MSCryptoSuite * linphone_core_get_srtp_crypto_suites(LinphoneCore *lc){ const char *config=lp_config_get_string(lc->config,"sip","srtp_crypto_suites","AES_CM_128_HMAC_SHA1_80, AES_CM_128_HMAC_SHA1_32, AES_CM_256_HMAC_SHA1_80, AES_CM_256_HMAC_SHA1_32"); char *tmp=ms_strdup(config); char *sep; char *pos; char *nextpos; char *params; int found=0; MSCryptoSuite *result=NULL; pos=tmp; do{ sep=strchr(pos,','); if (!sep) { sep=pos+strlen(pos); nextpos=NULL; }else { *sep='\0'; nextpos=sep+1; } while(*pos==' ') ++pos; /*strip leading spaces*/ params=strchr(pos,' '); /*look for params that arrive after crypto suite name*/ if (params){ while(*params==' ') ++params; /*strip parameters leading space*/ } if (sep-pos>0){ MSCryptoSuiteNameParams np; MSCryptoSuite suite; np.name=pos; np.params=params; suite=ms_crypto_suite_build_from_name_params(&np); if (suite!=MS_CRYPTO_SUITE_INVALID){ result=ms_realloc(result,(found+2)*sizeof(MSCryptoSuite)); result[found]=suite; result[found+1]=MS_CRYPTO_SUITE_INVALID; found++; ms_message("Configured srtp crypto suite: %s %s",np.name,np.params ? np.params : ""); } } pos=nextpos; }while(pos); ms_free(tmp); if (lc->rtp_conf.srtp_suites){ ms_free(lc->rtp_conf.srtp_suites); lc->rtp_conf.srtp_suites=NULL; } lc->rtp_conf.srtp_suites=result; return result; } static char * seperate_string_list(char **str) { char *ret; if (str == NULL) return NULL; if (*str == NULL) return NULL; if (**str == '\0') return NULL; ret = *str; for ( ; **str!='\0' && **str!=' ' && **str!=','; (*str)++); if (**str == '\0') { return ret; } else { **str = '\0'; do { (*str)++; } while (**str!='\0' && (**str==' ' || **str==',')); return ret; } } MsZrtpCryptoTypesCount linphone_core_get_zrtp_key_agreement_suites(LinphoneCore *lc, MSZrtpKeyAgreement keyAgreements[MS_MAX_ZRTP_CRYPTO_TYPES]){ char * zrtpConfig = (char*)lp_config_get_string(lc->config, "sip", "zrtp_key_agreements_suites", NULL); MsZrtpCryptoTypesCount key_agreements_count = 0; char * entry, * origPtr; if (zrtpConfig == NULL) { return 0; } origPtr = strdup(zrtpConfig); zrtpConfig = origPtr; while ((entry = seperate_string_list(&zrtpConfig))) { const MSZrtpKeyAgreement agreement = ms_zrtp_key_agreement_from_string(entry); if (agreement != MS_ZRTP_KEY_AGREEMENT_INVALID) { ms_message("Configured zrtp key agreement: '%s'", ms_zrtp_key_agreement_to_string(agreement)); keyAgreements[key_agreements_count++] = agreement; } } free(origPtr); return key_agreements_count; } MsZrtpCryptoTypesCount linphone_core_get_zrtp_cipher_suites(LinphoneCore *lc, MSZrtpCipher ciphers[MS_MAX_ZRTP_CRYPTO_TYPES]){ char * zrtpConfig = (char*)lp_config_get_string(lc->config, "sip", "zrtp_cipher_suites", NULL); MsZrtpCryptoTypesCount cipher_count = 0; char * entry, * origPtr; if (zrtpConfig == NULL) { return 0; } origPtr = strdup(zrtpConfig); zrtpConfig = origPtr; while ((entry = seperate_string_list(&zrtpConfig))) { const MSZrtpCipher cipher = ms_zrtp_cipher_from_string(entry); if (cipher != MS_ZRTP_CIPHER_INVALID) { ms_message("Configured zrtp cipher: '%s'", ms_zrtp_cipher_to_string(cipher)); ciphers[cipher_count++] = cipher; } } free(origPtr); return cipher_count; } MsZrtpCryptoTypesCount linphone_core_get_zrtp_hash_suites(LinphoneCore *lc, MSZrtpHash hashes[MS_MAX_ZRTP_CRYPTO_TYPES]){ char * zrtpConfig = (char*)lp_config_get_string(lc->config, "sip", "zrtp_hash_suites", NULL); MsZrtpCryptoTypesCount hash_count = 0; char * entry, * origPtr; if (zrtpConfig == NULL) { return 0; } origPtr = strdup(zrtpConfig); zrtpConfig = origPtr; while ((entry = seperate_string_list(&zrtpConfig))) { const MSZrtpHash hash = ms_zrtp_hash_from_string(entry); if (hash != MS_ZRTP_HASH_INVALID) { ms_message("Configured zrtp hash: '%s'", ms_zrtp_hash_to_string(hash)); hashes[hash_count++] = hash; } } free(origPtr); return hash_count; } MsZrtpCryptoTypesCount linphone_core_get_zrtp_auth_suites(LinphoneCore *lc, MSZrtpAuthTag authTags[MS_MAX_ZRTP_CRYPTO_TYPES]){ char * zrtpConfig = (char*)lp_config_get_string(lc->config, "sip", "zrtp_auth_suites", NULL); MsZrtpCryptoTypesCount auth_tag_count = 0; char * entry, * origPtr; if (zrtpConfig == NULL) { return 0; } origPtr = strdup(zrtpConfig); zrtpConfig = origPtr; while ((entry = seperate_string_list(&zrtpConfig))) { const MSZrtpAuthTag authTag = ms_zrtp_auth_tag_from_string(entry); if (authTag != MS_ZRTP_AUTHTAG_INVALID) { ms_message("Configured zrtp auth tag: '%s'", ms_zrtp_auth_tag_to_string(authTag)); authTags[auth_tag_count++] = authTag; } } free(origPtr); return auth_tag_count; } MsZrtpCryptoTypesCount linphone_core_get_zrtp_sas_suites(LinphoneCore *lc, MSZrtpSasType sasTypes[MS_MAX_ZRTP_CRYPTO_TYPES]){ char * zrtpConfig = (char*)lp_config_get_string(lc->config, "sip", "zrtp_sas_suites", NULL); MsZrtpCryptoTypesCount sas_count = 0; char * entry, * origPtr; if (zrtpConfig == NULL) { return 0; } origPtr = strdup(zrtpConfig); zrtpConfig = origPtr; while ((entry = seperate_string_list(&zrtpConfig))) { const MSZrtpSasType type = ms_zrtp_sas_type_from_string(entry); if (type != MS_ZRTP_SAS_INVALID) { ms_message("Configured zrtp SAS type: '%s'", ms_zrtp_sas_type_to_string(type)); sasTypes[sas_count++] = type; } } free(origPtr); return sas_count; } const char ** linphone_core_get_supported_file_formats(LinphoneCore *core){ static const char *mkv="mkv"; static const char *wav="wav"; if (core->supported_formats==NULL){ core->supported_formats=ms_malloc0(3*sizeof(char*)); core->supported_formats[0]=wav; if (ms_factory_lookup_filter_by_id(ms_factory_get_fallback(),MS_MKV_RECORDER_ID)){ core->supported_formats[1]=mkv; } } return core->supported_formats; } bool_t linphone_core_symmetric_rtp_enabled(LinphoneCore*lc){ return lp_config_get_int(lc->config,"rtp","symmetric",1); } int linphone_core_set_network_simulator_params(LinphoneCore *lc, const OrtpNetworkSimulatorParams *params){ if (params!=&lc->net_conf.netsim_params) lc->net_conf.netsim_params=*params; /*TODO: should we make some sanity checks on the parameters here*/ return 0; } const OrtpNetworkSimulatorParams *linphone_core_get_network_simulator_params(const LinphoneCore *lc){ return &lc->net_conf.netsim_params; } static const char *_tunnel_mode_str[3] = { "disable", "enable", "auto" }; LinphoneTunnelMode linphone_tunnel_mode_from_string(const char *string) { if(string != NULL) { int i; for(i=0; i<3 && strcmp(string, _tunnel_mode_str[i]) != 0; i++); if(i<3) { return (LinphoneTunnelMode)i; } else { ms_error("Invalid tunnel mode '%s'", string); return LinphoneTunnelModeDisable; } } else { return LinphoneTunnelModeDisable; } } const char *linphone_tunnel_mode_to_string(LinphoneTunnelMode mode) { switch(mode){ case LinphoneTunnelModeAuto: return "auto"; case LinphoneTunnelModeDisable: return "disable"; case LinphoneTunnelModeEnable: return "enable"; } return "invalid"; } |
[Prev in Thread] | Current Thread | [Next in Thread] |