diff --git a/coreapi/private_functions.h b/coreapi/private_functions.h index df615255b..e6a27fbe0 100644 --- a/coreapi/private_functions.h +++ b/coreapi/private_functions.h @@ -47,6 +47,7 @@ void linphone_call_notify_transfer_state_changed(LinphoneCall *call, LinphoneCal void linphone_call_notify_stats_updated(LinphoneCall *call, const LinphoneCallStats *stats); void linphone_call_notify_info_message_received(LinphoneCall *call, const LinphoneInfoMessage *msg); void linphone_call_notify_ack_processing(LinphoneCall *call, LinphoneHeaders *msg, bool_t is_received); +void linphone_call_notify_tmmbr_received(LinphoneCall *call, int stream_index, int tmmbr); LinphoneCall * linphone_call_new_outgoing(struct _LinphoneCore *lc, const LinphoneAddress *from, const LinphoneAddress *to, const LinphoneCallParams *params, LinphoneProxyConfig *cfg); LinphoneCall * linphone_call_new_incoming(struct _LinphoneCore *lc, const LinphoneAddress *from, const LinphoneAddress *to, LinphonePrivate::SalCallOp *op); diff --git a/include/linphone/api/c-call-cbs.h b/include/linphone/api/c-call-cbs.h index ed5c3718c..207321ddf 100644 --- a/include/linphone/api/c-call-cbs.h +++ b/include/linphone/api/c-call-cbs.h @@ -159,6 +159,20 @@ LINPHONE_PUBLIC LinphoneCallCbsAckProcessingCb linphone_call_cbs_get_ack_process */ LINPHONE_PUBLIC void linphone_call_cbs_set_ack_processing (LinphoneCallCbs *cbs, LinphoneCallCbsAckProcessingCb cb); +/** + * Get the TMMBR received callback. + * @param[in] cbs LinphoneCallCbs object. + * @return The current TMMBR received callback. + */ +LINPHONE_PUBLIC LinphoneCallCbsTmmbrReceivedCb linphone_call_cbs_get_tmmbr_received(LinphoneCallCbs *cbs); + +/** + * Set the TMMBR received callback. + * @param[in] cbs LinphoneCallCbs object. + * @param[in] cb The TMMBR received callback to be used. + */ +LINPHONE_PUBLIC void linphone_call_cbs_set_tmmbr_received(LinphoneCallCbs *cbs, LinphoneCallCbsTmmbrReceivedCb cb); + /** * @} */ diff --git a/include/linphone/api/c-callbacks.h b/include/linphone/api/c-callbacks.h index f8b796a00..339963691 100644 --- a/include/linphone/api/c-callbacks.h +++ b/include/linphone/api/c-callbacks.h @@ -87,6 +87,14 @@ typedef void (*LinphoneCallCbsTransferStateChangedCb)(LinphoneCall *call, Linpho */ typedef void (*LinphoneCallCbsAckProcessingCb)(LinphoneCall *call, LinphoneHeaders *ack, bool_t is_received); +/** + * Callback for notifying a received TMMBR. + * @param call LinphoneCall for which the TMMBR has changed + * @param stream_index the index of the current stream + * @param tmmbr the value of the received TMMBR + */ +typedef void (*LinphoneCallCbsTmmbrReceivedCb)(LinphoneCall *call, int stream_index, int tmmbr); + /** * @} **/ diff --git a/src/c-wrapper/api/c-call-cbs.cpp b/src/c-wrapper/api/c-call-cbs.cpp index 17b521e40..903dfa26a 100644 --- a/src/c-wrapper/api/c-call-cbs.cpp +++ b/src/c-wrapper/api/c-call-cbs.cpp @@ -33,6 +33,7 @@ struct _LinphoneCallCbs { LinphoneCallCbsStatsUpdatedCb statsUpdatedCb; LinphoneCallCbsTransferStateChangedCb transferStateChangedCb; LinphoneCallCbsAckProcessingCb ackProcessing; + LinphoneCallCbsTmmbrReceivedCb tmmbrReceivedCb; }; BELLE_SIP_DECLARE_VPTR_NO_EXPORT(LinphoneCallCbs); @@ -124,3 +125,11 @@ LinphoneCallCbsAckProcessingCb linphone_call_cbs_get_ack_processing (LinphoneCal void linphone_call_cbs_set_ack_processing (LinphoneCallCbs *cbs, LinphoneCallCbsAckProcessingCb cb){ cbs->ackProcessing = cb; } + +LinphoneCallCbsTmmbrReceivedCb linphone_call_cbs_get_tmmbr_received (LinphoneCallCbs *cbs) { + return cbs->tmmbrReceivedCb; +} + +void linphone_call_cbs_set_tmmbr_received (LinphoneCallCbs *cbs, LinphoneCallCbsTmmbrReceivedCb cb) { + cbs->tmmbrReceivedCb = cb; +} diff --git a/src/c-wrapper/api/c-call.cpp b/src/c-wrapper/api/c-call.cpp index 07159bfc4..0fa450fa5 100644 --- a/src/c-wrapper/api/c-call.cpp +++ b/src/c-wrapper/api/c-call.cpp @@ -206,6 +206,10 @@ void linphone_call_notify_ack_processing (LinphoneCall *call, LinphoneHeaders *m NOTIFY_IF_EXIST(AckProcessing, ack_processing, call, msg, is_received) } +void linphone_call_notify_tmmbr_received (LinphoneCall *call, int stream_index, int tmmbr) { + NOTIFY_IF_EXIST(TmmbrReceived, tmmbr_received, call, stream_index, tmmbr) +} + // ============================================================================= // Public functions. diff --git a/src/call/call-p.h b/src/call/call-p.h index 7b2754ff6..0477198df 100644 --- a/src/call/call-p.h +++ b/src/call/call-p.h @@ -110,6 +110,7 @@ private: bool areSoundResourcesAvailable (const std::shared_ptr &session) override; bool isPlayingRingbackTone (const std::shared_ptr &session) override; void onRealTimeTextCharacterReceived (const std::shared_ptr &session, RealtimeTextReceivedCharacter *character) override; + void onTmmbrReceived(const std::shared_ptr &session, int streamIndex, int tmmbr) override; mutable LinphonePlayer *player = nullptr; diff --git a/src/call/call.cpp b/src/call/call.cpp index f60387011..6d51ce781 100644 --- a/src/call/call.cpp +++ b/src/call/call.cpp @@ -491,6 +491,11 @@ void CallPrivate::onRealTimeTextCharacterReceived (const shared_ptr getChatRoom()->getPrivate()->realtimeTextReceived(data->character, q->getSharedFromThis()); } +void CallPrivate::onTmmbrReceived (const shared_ptr &session, int streamIndex, int tmmbr) { + L_Q(); + linphone_call_notify_tmmbr_received(L_GET_C_BACK_PTR(q), streamIndex, tmmbr); +} + // ============================================================================= Call::Call (CallPrivate &p, shared_ptr core) : Object(p), CoreAccessor(core) { diff --git a/src/conference/session/call-session-listener.h b/src/conference/session/call-session-listener.h index a334614ea..edc634f22 100644 --- a/src/conference/session/call-session-listener.h +++ b/src/conference/session/call-session-listener.h @@ -54,6 +54,7 @@ public: virtual void onIncomingCallSessionTimeoutCheck (const std::shared_ptr &session, int elapsed, bool oneSecondElapsed) {} virtual void onInfoReceived (const std::shared_ptr &session, const LinphoneInfoMessage *im) {} virtual void onNoMediaTimeoutCheck (const std::shared_ptr &session, bool oneSecondElapsed) {} + virtual void onTmmbrReceived (const std::shared_ptr &session, int streamIndex, int tmmbr) {} virtual void onEncryptionChanged (const std::shared_ptr &session, bool activated, const std::string &authToken) {} diff --git a/src/conference/session/media-session.cpp b/src/conference/session/media-session.cpp index d79b6a34a..955bcbb4e 100644 --- a/src/conference/session/media-session.cpp +++ b/src/conference/session/media-session.cpp @@ -2238,6 +2238,7 @@ void MediaSessionPrivate::handleIceEvents (OrtpEvent *ev) { } void MediaSessionPrivate::handleStreamEvents (int streamIndex) { + L_Q(); MediaStream *ms = (streamIndex == mainAudioStreamIndex) ? &audioStream->ms : (streamIndex == mainVideoStreamIndex ? &videoStream->ms : &textStream->ms); if (ms) { @@ -2272,14 +2273,28 @@ void MediaSessionPrivate::handleStreamEvents (int streamIndex) { stats = videoStats; else stats = textStats; + + OrtpEventType evt = ortp_event_get_type(ev); + OrtpEventData *evd = ortp_event_get_data(ev); + + /*This MUST be done before any call to "linphone_call_stats_fill" since it has ownership over evd->packet*/ + if (evt == ORTP_EVENT_RTCP_PACKET_RECEIVED) { + do { + if (evd->packet && rtcp_is_RTPFB(evd->packet)) { + if (rtcp_RTPFB_get_type(evd->packet) == RTCP_RTPFB_TMMBR) { + listener->onTmmbrReceived(q->getSharedFromThis(), streamIndex, (int)rtcp_RTPFB_tmmbr_get_max_bitrate(evd->packet)); + } + } + } while (rtcp_next_packet(evd->packet)); + rtcp_rewind(evd->packet); + } + /* And yes the MediaStream must be taken at each iteration, because it may have changed due to the handling of events * in this loop*/ ms = getMediaStream(streamIndex); if (ms) linphone_call_stats_fill(stats, ms, ev); notifyStatsUpdated(streamIndex); - OrtpEventType evt = ortp_event_get_type(ev); - OrtpEventData *evd = ortp_event_get_data(ev); if (evt == ORTP_EVENT_ZRTP_ENCRYPTION_CHANGED) { if (streamIndex == mainAudioStreamIndex) audioStreamEncryptionChanged(!!evd->info.zrtp_stream_encrypted); diff --git a/tester/call_video_tester.c b/tester/call_video_tester.c index cf55ad1f1..d74fbd2f8 100644 --- a/tester/call_video_tester.c +++ b/tester/call_video_tester.c @@ -2008,6 +2008,70 @@ static void video_call_with_thin_congestion(void){ linphone_core_manager_destroy(pauline); } +static void on_tmmbr_received(LinphoneCall *call, int stream_index, int tmmbr) { + if (stream_index == _linphone_call_get_main_video_stream_index(call)) { + stats* stat = get_stats(linphone_call_get_core(call)); + stat->tmmbr_received_from_cb = tmmbr; + } +} + +static void call_created(LinphoneCore *lc, LinphoneCall *call) { + LinphoneCallCbs *cbs = linphone_factory_create_call_cbs(linphone_factory_get()); + linphone_call_cbs_set_tmmbr_received(cbs, on_tmmbr_received); + linphone_call_add_callbacks(call, cbs); + linphone_call_cbs_unref(cbs); +} + +/* + * This test simulates a higher bandwith available from marie than expected. + * The stream from pauline to marie is not under test. + * It checks that after a few seconds marie received a TMMBR with the approximate value corresponding to the new bandwidth. + * +**/ +static void video_call_with_high_bandwidth_available(void) { + LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc"); + LinphoneCoreManager *pauline = linphone_core_manager_new("pauline_rc"); + LinphoneVideoPolicy pol = {0}; + OrtpNetworkSimulatorParams simparams = { 0 }; + LinphoneCoreCbs *core_cbs = linphone_factory_create_core_cbs(linphone_factory_get()); + + linphone_core_set_video_device(marie->lc, "Mire: Mire (synthetic moving picture)"); + linphone_core_enable_video_capture(marie->lc, TRUE); + linphone_core_enable_video_display(marie->lc, TRUE); + linphone_core_enable_video_capture(pauline->lc, TRUE); + linphone_core_enable_video_display(pauline->lc, TRUE); + + pol.automatically_accept = TRUE; + pol.automatically_initiate = TRUE; + linphone_core_set_video_policy(marie->lc, &pol); + linphone_core_set_video_policy(pauline->lc, &pol); + + linphone_core_set_preferred_video_size_by_name(marie->lc, "vga"); + simparams.mode = OrtpNetworkSimulatorOutbound; + simparams.enabled = TRUE; + simparams.max_bandwidth = 1000000; + simparams.max_buffer_size = (int)simparams.max_bandwidth; + simparams.latency = 60; + + linphone_core_set_network_simulator_params(marie->lc, &simparams); + + linphone_core_cbs_set_call_created(core_cbs, call_created); + linphone_core_add_callbacks(marie->lc, core_cbs); + + if (BC_ASSERT_TRUE(call(marie, pauline))){ + /*wait a little in order to have traffic*/ + BC_ASSERT_TRUE(wait_for_until(marie->lc, pauline->lc, NULL, 5, 50000)); + + BC_ASSERT_GREATER((float)marie->stat.last_tmmbr_value_received, 870000.f, float, "%f"); + BC_ASSERT_LOWER((float)marie->stat.last_tmmbr_value_received, 1150000.f, float, "%f"); + + end_call(marie, pauline); + } + linphone_core_cbs_unref(core_cbs); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + test_t call_video_tests[] = { #ifdef VIDEO_ENABLED TEST_NO_TAG("Call paused resumed with video", call_paused_resumed_with_video), @@ -2073,10 +2137,11 @@ test_t call_video_tests[] = { TEST_NO_TAG("Classic video entry phone setup", classic_video_entry_phone_setup), TEST_NO_TAG("Incoming REINVITE with invalid SDP in ACK", incoming_reinvite_with_invalid_ack_sdp), TEST_NO_TAG("Outgoing REINVITE with invalid SDP in ACK", outgoing_reinvite_with_invalid_ack_sdp), -#endif TEST_NO_TAG("Video call with no audio and no video codec", video_call_with_no_audio_and_no_video_codec), TEST_NO_TAG("Call with early media and no SDP in 200 Ok with video", call_with_early_media_and_no_sdp_in_200_with_video), - TEST_NO_TAG("Video call with thin congestion", video_call_with_thin_congestion) + TEST_NO_TAG("Video call with thin congestion", video_call_with_thin_congestion), + TEST_NO_TAG("Video call with high bandwidth available", video_call_with_high_bandwidth_available) +#endif }; test_suite_t call_video_test_suite = {"Video Call", NULL, NULL, liblinphone_tester_before_each, liblinphone_tester_after_each, diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h index 7e07493d0..9824c2084 100644 --- a/tester/liblinphone_tester.h +++ b/tester/liblinphone_tester.h @@ -283,6 +283,7 @@ typedef struct _stats { int number_of_rtcp_generic_nack; int number_of_tmmbr_received; int last_tmmbr_value_received; + int tmmbr_received_from_cb; int number_of_participants_added; int number_of_participant_admin_statuses_changed;