diff --git a/coreapi/help/doxygen.dox b/coreapi/help/doxygen.dox index 99293c65b..7d0a82849 100644 --- a/coreapi/help/doxygen.dox +++ b/coreapi/help/doxygen.dox @@ -43,6 +43,13 @@ * **/ +/** + * @defgroup call_misc Obtaining information about a running call: sound volumes, quality indicators + * + * When a call is running, it is possible to retrieve in real time current measured volumes and quality indicator. + * +**/ + /** * @defgroup media_parameters Controlling media parameters **/ diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index ab521aa7f..fdcca58d1 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -585,6 +585,17 @@ void linphone_call_params_set_audio_bandwidth_limit(LinphoneCallParams *cp, int cp->audio_bw=bandwidth; } +#ifdef VIDEO_ENABLED +/** + * Request remote side to send us a Video Fast Update. +**/ +void linphone_call_send_vfu_request(LinphoneCall *call) +{ + if (LinphoneCallStreamsRunning == linphone_call_get_state(call)) + sal_call_send_vfu_request(call->op); +} +#endif + /** * **/ @@ -1040,16 +1051,7 @@ void linphone_call_stop_media_streams(LinphoneCall *call){ } } -#ifdef VIDEO_ENABLED -/** - * Request remote side to send us VFU. -**/ -void linphone_call_send_vfu_request(LinphoneCall *call) -{ - if (LinphoneCallStreamsRunning == linphone_call_get_state(call)) - sal_call_send_vfu_request(call->op); -} -#endif + void linphone_call_enable_echo_cancellation(LinphoneCall *call, bool_t enable) { if (call!=NULL && call->audiostream!=NULL && call->audiostream->ec){ @@ -1089,6 +1091,11 @@ bool_t linphone_call_echo_limiter_enabled(const LinphoneCall *call){ } } +/** + * @addtogroup call_misc + * @{ +**/ + /** * Returns the measured sound volume played locally (received from remote) * It is expressed in dbm0. @@ -1119,6 +1126,45 @@ float linphone_call_get_record_volume(LinphoneCall *call){ return LINPHONE_VOLUME_DB_LOWEST; } +/** + * Obtain real-time quality rating of the call + * + * Based on local RTP statistics and RTCP feedback, a quality rating is computed and updated + * during all the duration of the call. This function returns its value at the time of the function call. + * It is expected that the rating is updated at least every 5 seconds or so. + * The rating is a floating point number comprised between 0 and 5. + * + * 4-5 = good quality
+ * 3-4 = average quality
+ * 2-3 = poor quality
+ * 1-2 = very poor quality
+ * 0-1 = can't be worse, mostly unusable
+ * + * @returns The function returns -1 if no quality measurement is available, for example if no + * active audio stream exist. Otherwise it returns the quality rating. +**/ +float linphone_call_get_current_quality(LinphoneCall *call){ + if (call->audiostream){ + return audio_stream_get_quality_rating(call->audiostream); + } + return -1; +} + +/** + * Returns call quality averaged over all the duration of the call. + * + * See linphone_call_get_current_quality() for more details about quality measurement. +**/ +float linphone_call_get_average_quality(LinphoneCall *call){ + if (call->audiostream){ + return audio_stream_get_average_quality_rating(call->audiostream); + } + return -1; +} + +/** + * @} +**/ static void display_bandwidth(RtpSession *as, RtpSession *vs){ ms_message("bandwidth usage: audio=[d=%.1f,u=%.1f] video=[d=%.1f,u=%.1f] kbit/sec", @@ -1171,6 +1217,8 @@ void linphone_call_background_tasks(LinphoneCall *call, bool_t one_second_elapse if (call->videostream!=NULL) video_stream_iterate(call->videostream); #endif + if (call->audiostream!=NULL) + audio_stream_iterate(call->audiostream); if (one_second_elapsed && call->audiostream!=NULL && disconnect_timeout>0 ) disconnected=!audio_stream_alive(call->audiostream,disconnect_timeout); if (disconnected) diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index 50fdb96d8..cd57b926d 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -255,6 +255,8 @@ LinphoneReason linphone_call_get_reason(const LinphoneCall *call); const char *linphone_call_get_remote_user_agent(LinphoneCall *call); float linphone_call_get_play_volume(LinphoneCall *call); float linphone_call_get_record_volume(LinphoneCall *call); +float linphone_call_get_current_quality(LinphoneCall *call); +float linphone_call_get_average_quality(LinphoneCall *call); void *linphone_call_get_user_pointer(LinphoneCall *call); void linphone_call_set_user_pointer(LinphoneCall *call, void *user_pointer); /** diff --git a/gtk/incall_view.c b/gtk/incall_view.c index 96f672cfc..919adaa3c 100644 --- a/gtk/incall_view.c +++ b/gtk/incall_view.c @@ -205,7 +205,6 @@ void linphone_gtk_in_call_view_set_incoming(LinphoneCall *call, bool_t with_paus gtk_label_set_markup(GTK_LABEL(status),_("Incoming call")); gtk_widget_show_all(linphone_gtk_get_widget(callview,"answer_decline_panel")); - gtk_widget_hide(linphone_gtk_get_widget(callview,"duration_frame")); gtk_widget_hide(linphone_gtk_get_widget(callview,"mute_pause_buttons")); display_peer_name_in_label(callee,linphone_call_get_remote_address (call)); @@ -228,6 +227,57 @@ void linphone_gtk_in_call_view_set_incoming(LinphoneCall *call, bool_t with_paus }else gtk_image_set_from_stock(GTK_IMAGE(animation),GTK_STOCK_EXECUTE,GTK_ICON_SIZE_DIALOG); } +static void rating_to_color(float rating, GdkColor *color){ + const char *colorname="grey"; + if (rating>=4.0) + colorname="green"; + else if (rating>=3.0) + colorname="white"; + else if (rating>=2.0) + colorname="yellow"; + else if (rating>=1.0) + colorname="orange"; + else if (rating>=0) + colorname="red"; + if (!gdk_color_parse(colorname,color)){ + g_warning("Fail to parse color %s",colorname); + } +} + +static const char *rating_to_text(float rating){ + if (rating>=4.0) + return _("good"); + if (rating>=3.0) + return _("average"); + if (rating>=2.0) + return _("poor"); + if (rating>=1.0) + return _("very poor"); + if (rating>=0) + return _("too bad"); + return _("unavailable"); +} + +static gboolean linphone_gtk_in_call_view_refresh(LinphoneCall *call){ + GtkWidget *callview=(GtkWidget*)linphone_call_get_user_pointer(call); + GtkWidget *qi=linphone_gtk_get_widget(callview,"quality_indicator"); + float rating=linphone_call_get_current_quality(call); + GdkColor color; + gchar tmp[50]; + linphone_gtk_in_call_view_update_duration(call); + if (rating>=0){ + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(qi),rating/5.0); + snprintf(tmp,sizeof(tmp),"%.1f (%s)",rating,rating_to_text(rating)); + gtk_progress_bar_set_text(GTK_PROGRESS_BAR(qi),tmp); + }else{ + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(qi),0); + gtk_progress_bar_set_text(GTK_PROGRESS_BAR(qi),_("unavailable")); + } + rating_to_color(rating,&color); + gtk_widget_modify_bg(qi,GTK_STATE_NORMAL,&color); + return TRUE; +} + void linphone_gtk_in_call_view_set_in_call(LinphoneCall *call){ GtkWidget *callview=(GtkWidget*)linphone_call_get_user_pointer(call); GtkWidget *status=linphone_gtk_get_widget(callview,"in_call_status"); @@ -235,10 +285,10 @@ void linphone_gtk_in_call_view_set_in_call(LinphoneCall *call){ GtkWidget *duration=linphone_gtk_get_widget(callview,"in_call_duration"); GtkWidget *animation=linphone_gtk_get_widget(callview,"in_call_animation"); GdkPixbufAnimation *pbuf=create_pixbuf_animation("incall_anim.gif"); + guint taskid=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(callview),"taskid")); display_peer_name_in_label(callee,linphone_call_get_remote_address (call)); - gtk_widget_show(linphone_gtk_get_widget(callview,"duration_frame")); gtk_widget_show(linphone_gtk_get_widget(callview,"mute_pause_buttons")); gtk_widget_hide(linphone_gtk_get_widget(callview,"answer_decline_panel")); gtk_label_set_markup(GTK_LABEL(status),_("In call")); @@ -250,6 +300,10 @@ void linphone_gtk_in_call_view_set_in_call(LinphoneCall *call){ }else gtk_image_set_from_stock(GTK_IMAGE(animation),GTK_STOCK_EXECUTE,GTK_ICON_SIZE_DIALOG); linphone_gtk_enable_mute_button( GTK_BUTTON(linphone_gtk_get_widget(callview,"incall_mute")),TRUE); + if (taskid==0){ + taskid=g_timeout_add(250,(GSourceFunc)linphone_gtk_in_call_view_refresh,call); + g_object_set_data(G_OBJECT(callview),"taskid",GINT_TO_POINTER(taskid)); + } } void linphone_gtk_in_call_view_set_paused(LinphoneCall *call){ @@ -283,6 +337,7 @@ void linphone_gtk_in_call_view_terminate(LinphoneCall *call, const char *error_m GtkWidget *status=linphone_gtk_get_widget(callview,"in_call_status"); GtkWidget *animation=linphone_gtk_get_widget(callview,"in_call_animation"); GdkPixbuf *pbuf=create_pixbuf(linphone_gtk_get_ui_config("stop_call_icon","stopcall-red.png")); + guint taskid=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(callview),"taskid")); if (error_msg==NULL) gtk_label_set_markup(GTK_LABEL(status),_("Call ended.")); @@ -299,6 +354,7 @@ void linphone_gtk_in_call_view_terminate(LinphoneCall *call, const char *error_m linphone_gtk_enable_mute_button( GTK_BUTTON(linphone_gtk_get_widget(callview,"incall_mute")),FALSE); linphone_gtk_enable_hold_button(call,FALSE,TRUE); + if (taskid!=0) g_source_remove(taskid); g_timeout_add_seconds(2,(GSourceFunc)in_call_view_terminated,call); } diff --git a/gtk/main.c b/gtk/main.c index b47296f27..6968466d8 100644 --- a/gtk/main.c +++ b/gtk/main.c @@ -632,15 +632,6 @@ void linphone_gtk_call_terminated(LinphoneCall *call, const char *error){ update_video_title(); } -static gboolean in_call_timer(){ - LinphoneCall *call=linphone_core_get_current_call(linphone_gtk_get_core()); - if (call){ - linphone_gtk_in_call_view_update_duration(call); - return TRUE; - } - return FALSE; -} - static bool_t all_other_calls_paused(LinphoneCall *refcall, const MSList *calls){ for(;calls!=NULL;calls=calls->next){ LinphoneCall *call=(LinphoneCall*)calls->data; @@ -961,7 +952,6 @@ static void linphone_gtk_call_state_changed(LinphoneCore *lc, LinphoneCall *call linphone_gtk_enable_mute_button( GTK_BUTTON(linphone_gtk_get_widget(linphone_gtk_get_main_window(),"main_mute")), TRUE); - g_timeout_add(250,(GSourceFunc)in_call_timer,NULL); break; case LinphoneCallError: linphone_gtk_in_call_view_terminate (call,msg); diff --git a/gtk/main.ui b/gtk/main.ui index aaa4ae4a4..c24591a91 100644 --- a/gtk/main.ui +++ b/gtk/main.ui @@ -1,4 +1,4 @@ - + @@ -62,7 +62,6 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -234,7 +233,6 @@ True - vertical True @@ -285,7 +283,7 @@ True True Enter username, phone number, or full sip address - + @@ -337,7 +335,6 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -345,7 +342,6 @@ True - vertical True @@ -361,7 +357,6 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -381,7 +376,7 @@ True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + @@ -459,10 +454,10 @@ True True - + + - @@ -702,7 +697,6 @@ True - vertical True @@ -1089,7 +1083,6 @@ True - vertical True @@ -1144,7 +1137,7 @@ True True - + 1 @@ -1156,7 +1149,7 @@ True True False - + 1 @@ -1334,7 +1327,6 @@ True - vertical True @@ -1346,22 +1338,10 @@ - + True - 0 - - - True - label - center - - - - - True - True - - + label + center 1 @@ -1405,39 +1385,6 @@ 2 - - - True - 0 - - - True - vertical - - - True - Duration - center - - - 0 - - - - - - - True - Duration: - True - - - - - False - 3 - - True @@ -1474,7 +1421,7 @@ False False - 4 + 3 @@ -1482,11 +1429,39 @@ - + True - In call - True - center + True + + + True + In call + True + center + + + 0 + + + + + True + Duration + center + + + 1 + + + + + True + Call quality rating + + + 2 + + diff --git a/mediastreamer2 b/mediastreamer2 index ef298ba27..3c6e9bf14 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit ef298ba2719c94f2de563deb7a8ef441d45794fc +Subproject commit 3c6e9bf14efefa4ddd078d7b24ce46b5177da3c7 diff --git a/oRTP b/oRTP index 7b39874da..6e9ac1d1f 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit 7b39874dae3c514901d40cd62d648ab2f849fdff +Subproject commit 6e9ac1d1f7c60f60bd67656c92ae801c61f41b27