diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index d5366e5c6..ee0388604 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -2230,6 +2230,27 @@ static void handle_ice_events(LinphoneCall *call, OrtpEvent *ev){ } } +// this method should be put in mediastreamer but has been kept here, private, +// to save time. +void linphone_call_stream_stats_hack(MediaStream* stream, LinphoneCallStats *stats, OrtpEvent *ev) { + OrtpEventType evt=ortp_event_get_type(ev); + OrtpEventData *evd=ortp_event_get_data(ev); + if (evt == ORTP_EVENT_RTCP_PACKET_RECEIVED) { + stats->round_trip_delay = rtp_session_get_round_trip_propagation(stream->session); + if(stats->received_rtcp != NULL) freemsg(stats->received_rtcp); + stats->received_rtcp = evd->packet; + evd->packet = NULL; + update_local_stats(stats,stream); + + } else if (evt == ORTP_EVENT_RTCP_PACKET_EMITTED) { + memcpy(&stats->jitter_stats, rtp_session_get_jitter_stats(stream->session), sizeof(jitter_stats_t)); + if(stats->sent_rtcp != NULL) freemsg(stats->sent_rtcp); + stats->sent_rtcp = evd->packet; + evd->packet = NULL; + update_local_stats(stats,stream); + } +} + void linphone_call_background_tasks(LinphoneCall *call, bool_t one_second_elapsed){ LinphoneCore* lc = call->core; int disconnect_timeout = linphone_core_get_nortp_timeout(call->core); @@ -2259,6 +2280,7 @@ void linphone_call_background_tasks(LinphoneCall *call, bool_t one_second_elapse #ifdef VIDEO_ENABLED if (call->videostream!=NULL) { OrtpEvent *ev; + LinphoneCallStats *stats=&call->stats[LINPHONE_CALL_STATS_VIDEO]; /* Ensure there is no dangling ICE check list. */ if (call->ice_session == NULL) call->videostream->ms.ice_check_list = NULL; @@ -2272,24 +2294,10 @@ void linphone_call_background_tasks(LinphoneCall *call, bool_t one_second_elapse OrtpEventData *evd=ortp_event_get_data(ev); if (evt == ORTP_EVENT_ZRTP_ENCRYPTION_CHANGED){ linphone_call_videostream_encryption_changed(call, evd->info.zrtp_stream_encrypted); - } else if (evt == ORTP_EVENT_RTCP_PACKET_RECEIVED) { - call->stats[LINPHONE_CALL_STATS_VIDEO].round_trip_delay = rtp_session_get_round_trip_propagation(call->videostream->ms.session); - if(call->stats[LINPHONE_CALL_STATS_VIDEO].received_rtcp != NULL) - freemsg(call->stats[LINPHONE_CALL_STATS_VIDEO].received_rtcp); - call->stats[LINPHONE_CALL_STATS_VIDEO].received_rtcp = evd->packet; - evd->packet = NULL; - update_local_stats(&call->stats[LINPHONE_CALL_STATS_VIDEO],(MediaStream*)call->videostream); + } else if (evt == ORTP_EVENT_RTCP_PACKET_RECEIVED || evt == ORTP_EVENT_RTCP_PACKET_EMITTED) { + linphone_call_stream_stats_hack(&call->videostream->ms, stats, ev); if (lc->vtable.call_stats_updated) - lc->vtable.call_stats_updated(lc, call, &call->stats[LINPHONE_CALL_STATS_VIDEO]); - } else if (evt == ORTP_EVENT_RTCP_PACKET_EMITTED) { - memcpy(&call->stats[LINPHONE_CALL_STATS_VIDEO].jitter_stats, rtp_session_get_jitter_stats(call->videostream->ms.session), sizeof(jitter_stats_t)); - if(call->stats[LINPHONE_CALL_STATS_VIDEO].sent_rtcp != NULL) - freemsg(call->stats[LINPHONE_CALL_STATS_VIDEO].sent_rtcp); - call->stats[LINPHONE_CALL_STATS_VIDEO].sent_rtcp = evd->packet; - evd->packet = NULL; - update_local_stats(&call->stats[LINPHONE_CALL_STATS_VIDEO],(MediaStream*)call->videostream); - if (lc->vtable.call_stats_updated) - lc->vtable.call_stats_updated(lc, call, &call->stats[LINPHONE_CALL_STATS_VIDEO]); + lc->vtable.call_stats_updated(lc, call, stats); } else if ((evt == ORTP_EVENT_ICE_SESSION_PROCESSING_FINISHED) || (evt == ORTP_EVENT_ICE_GATHERING_FINISHED) || (evt == ORTP_EVENT_ICE_LOSING_PAIRS_COMPLETED) || (evt == ORTP_EVENT_ICE_RESTART_NEEDED)) { handle_ice_events(call, ev); @@ -2300,6 +2308,7 @@ void linphone_call_background_tasks(LinphoneCall *call, bool_t one_second_elapse #endif if (call->audiostream!=NULL) { OrtpEvent *ev; + LinphoneCallStats *stats=&call->stats[LINPHONE_CALL_STATS_AUDIO]; /* Ensure there is no dangling ICE check list. */ if (call->ice_session == NULL) call->audiostream->ms.ice_check_list = NULL; @@ -2315,24 +2324,10 @@ void linphone_call_background_tasks(LinphoneCall *call, bool_t one_second_elapse linphone_call_audiostream_encryption_changed(call, evd->info.zrtp_stream_encrypted); } else if (evt == ORTP_EVENT_ZRTP_SAS_READY) { linphone_call_audiostream_auth_token_ready(call, evd->info.zrtp_sas.sas, evd->info.zrtp_sas.verified); - } else if (evt == ORTP_EVENT_RTCP_PACKET_RECEIVED) { - call->stats[LINPHONE_CALL_STATS_AUDIO].round_trip_delay = rtp_session_get_round_trip_propagation(call->audiostream->ms.session); - if(call->stats[LINPHONE_CALL_STATS_AUDIO].received_rtcp != NULL) - freemsg(call->stats[LINPHONE_CALL_STATS_AUDIO].received_rtcp); - call->stats[LINPHONE_CALL_STATS_AUDIO].received_rtcp = evd->packet; - evd->packet = NULL; - update_local_stats(&call->stats[LINPHONE_CALL_STATS_AUDIO],(MediaStream*)call->audiostream); + } else if (evt == ORTP_EVENT_RTCP_PACKET_RECEIVED || evt == ORTP_EVENT_RTCP_PACKET_EMITTED) { + linphone_call_stream_stats_hack(&call->audiostream->ms, stats, ev); if (lc->vtable.call_stats_updated) - lc->vtable.call_stats_updated(lc, call, &call->stats[LINPHONE_CALL_STATS_AUDIO]); - } else if (evt == ORTP_EVENT_RTCP_PACKET_EMITTED) { - memcpy(&call->stats[LINPHONE_CALL_STATS_AUDIO].jitter_stats, rtp_session_get_jitter_stats(call->audiostream->ms.session), sizeof(jitter_stats_t)); - if(call->stats[LINPHONE_CALL_STATS_AUDIO].sent_rtcp != NULL) - freemsg(call->stats[LINPHONE_CALL_STATS_AUDIO].sent_rtcp); - call->stats[LINPHONE_CALL_STATS_AUDIO].sent_rtcp = evd->packet; - evd->packet = NULL; - update_local_stats(&call->stats[LINPHONE_CALL_STATS_AUDIO],(MediaStream*)call->audiostream); - if (lc->vtable.call_stats_updated) - lc->vtable.call_stats_updated(lc, call, &call->stats[LINPHONE_CALL_STATS_AUDIO]); + lc->vtable.call_stats_updated(lc, call, stats); } else if ((evt == ORTP_EVENT_ICE_SESSION_PROCESSING_FINISHED) || (evt == ORTP_EVENT_ICE_GATHERING_FINISHED) || (evt == ORTP_EVENT_ICE_LOSING_PAIRS_COMPLETED) || (evt == ORTP_EVENT_ICE_RESTART_NEEDED)) { handle_ice_events(call, ev); diff --git a/coreapi/private.h b/coreapi/private.h index b5312bd60..0ba7c2db0 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -730,6 +730,7 @@ typedef enum _LinphoneToneID{ void linphone_core_play_named_tone(LinphoneCore *lc, LinphoneToneID id); bool_t linphone_core_tone_indications_enabled(LinphoneCore*lc); +void linphone_call_stream_stats_hack(MediaStream* stream, LinphoneCallStats *stats, OrtpEvent *ev); #ifdef __cplusplus } #endif diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 15979a318..4cd84bf47 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -11,6 +11,7 @@ linphone_daemon_SOURCES=daemon.cc \ commands/audio-codec-toggle.cc \ commands/audio-stream-start.cc \ commands/audio-stream-stop.cc \ + commands/audio-stream-stats.cc \ commands/call.cc \ commands/call-stats.cc \ commands/call-status.cc \ @@ -41,6 +42,7 @@ linphone_daemon_SOURCES=daemon.cc \ commands/audio-codec-toggle.h \ commands/audio-stream-start.h \ commands/audio-stream-stop.h \ + commands/audio-stream-stats.h \ commands/call.h \ commands/call-stats.h \ commands/call-status.h \ diff --git a/daemon/commands/audio-stream-stats.cc b/daemon/commands/audio-stream-stats.cc new file mode 100644 index 000000000..8ec208301 --- /dev/null +++ b/daemon/commands/audio-stream-stats.cc @@ -0,0 +1,46 @@ +#include "audio-stream-stats.h" + +using namespace std; + +AudioStreamStatsCommand::AudioStreamStatsCommand() : + DaemonCommand("audio-stream-stats", "audio-stream-stats ", "Return stats of a given audio stream.") { + addExample(new DaemonCommandExample("audio-stream-stats 1", + "Status: Ok\n\n" + "Audio-ICE state: Not activated\n" + "Audio-RoundTripDelay: 0.0859833\n" + "Audio-Jitter: 296\n" + "Audio-JitterBufferSizeMs: 47.7778\n" + "Audio-Received-InterarrivalJitter: 154\n" + "Audio-Received-FractionLost: 0\n" + "Audio-Sent-InterarrivalJitter: 296\n" + "Audio-Sent-FractionLost: 0\n" + "Audio-Payload-type-number: 111\n" + "Audio-Clock-rate: 16000\n" + "Audio-Bitrate: 44000\n" + "Audio-Mime: speex\n" + "Audio-Channels: 1\n" + "Audio-Recv-fmtp: vbr=on\n" + "Audio-Send-fmtp: vbr=on")); + addExample(new DaemonCommandExample("audio-stream-stats 2", + "Status: Error\n" + "Reason: No audio stream with such id.")); +} + +void AudioStreamStatsCommand::exec(Daemon *app, const char *args) { + int sid; + AudioStreamAndOther *stream = NULL; + if (sscanf(args, "%i", &sid) == 1) { + stream = app->findAudioStreamAndOther(sid); + if (!stream) { + app->sendResponse(Response("No audio stream with such id.")); + return; + } + } else { + app->sendResponse(Response("No stream specified.")); + return; + } + + ostringstream ostr; + ostr << AudioStreamStatsResponse(app, stream->stream, &stream->stats, false).getBody(); + app->sendResponse(Response(ostr.str().c_str(), Response::Ok)); +} diff --git a/daemon/commands/audio-stream-stats.h b/daemon/commands/audio-stream-stats.h new file mode 100644 index 000000000..c25746cd0 --- /dev/null +++ b/daemon/commands/audio-stream-stats.h @@ -0,0 +1,12 @@ +#ifndef COMMAND_AUDIO_STREAM_STATS_H_ +#define COMMAND_AUDIO_STREAM_STATS_H_ + +#include "../daemon.h" + +class AudioStreamStatsCommand: public DaemonCommand { +public: + AudioStreamStatsCommand(); + virtual void exec(Daemon *app, const char *args); +}; + +#endif //COMMAND_AUDIO_STREAM_STATS_H_ diff --git a/daemon/daemon.cc b/daemon/daemon.cc index 8fee5540e..2af8da2be 100644 --- a/daemon/daemon.cc +++ b/daemon/daemon.cc @@ -22,6 +22,7 @@ #include "commands/audio-codec-toggle.h" #include "commands/audio-stream-start.h" #include "commands/audio-stream-stop.h" +#include "commands/audio-stream-stats.h" #include "commands/call.h" #include "commands/call-stats.h" #include "commands/call-status.h" @@ -43,6 +44,7 @@ #include "commands/quit.h" #include "commands/version.h" +#include "private.h" using namespace std; #ifndef WIN32 @@ -99,25 +101,7 @@ DtmfResponse::DtmfResponse(Daemon *daemon, LinphoneCall *call, int dtmf) { ms_free(remote); } -CallStatsResponse::CallStatsResponse(Daemon *daemon, LinphoneCall *call, const LinphoneCallStats *stats, bool event) { - const LinphoneCallParams *callParams = linphone_call_get_current_params(call); - const char *prefix = ""; - - ostringstream ostr; - if (event) { - ostr << "Event-type: call-stats\n"; - ostr << "Id: " << daemon->updateCallId(call) << "\n"; - ostr << "Type: "; - if (stats->type == LINPHONE_CALL_STATS_AUDIO) { - ostr << "Audio"; - } else { - ostr << "Video"; - } - ostr << "\n"; - } else { - prefix = ((stats->type == LINPHONE_CALL_STATS_AUDIO) ? "Audio-" : "Video-"); - } - +static ostream &printCallStatsHelper(ostream &ostr, const LinphoneCallStats *stats, const string &prefix) { ostr << prefix << "ICE state: " << ice_state_str[stats->ice_state] << "\n"; ostr << prefix << "RoundTripDelay: " << stats->round_trip_delay << "\n"; ostr << prefix << "Jitter: " << stats->jitter_stats.jitter << "\n"; @@ -163,6 +147,29 @@ CallStatsResponse::CallStatsResponse(Daemon *daemon, LinphoneCall *call, const L ostr << prefix << "Sent-InterarrivalJitter: " << ij << "\n"; ostr << prefix << "Sent-FractionLost: " << flost << "\n"; } + return ostr; +} + +CallStatsResponse::CallStatsResponse(Daemon *daemon, LinphoneCall *call, const LinphoneCallStats *stats, bool event) { + const LinphoneCallParams *callParams = linphone_call_get_current_params(call); + const char *prefix = ""; + + ostringstream ostr; + if (event) { + ostr << "Event-type: call-stats\n"; + ostr << "Id: " << daemon->updateCallId(call) << "\n"; + ostr << "Type: "; + if (stats->type == LINPHONE_CALL_STATS_AUDIO) { + ostr << "Audio"; + } else { + ostr << "Video"; + } + ostr << "\n"; + } else { + prefix = ((stats->type == LINPHONE_CALL_STATS_AUDIO) ? "Audio-" : "Video-"); + } + + printCallStatsHelper(ostr, stats, prefix); if (stats->type == LINPHONE_CALL_STATS_AUDIO) { const PayloadType *audioCodec = linphone_call_params_get_used_audio_codec(callParams); @@ -175,6 +182,31 @@ CallStatsResponse::CallStatsResponse(Daemon *daemon, LinphoneCall *call, const L setBody(ostr.str().c_str()); } + +AudioStreamStatsResponse::AudioStreamStatsResponse(Daemon* daemon, AudioStream* stream, + const LinphoneCallStats *stats, bool event) { + const char *prefix = ""; + + ostringstream ostr; + if (event) { + ostr << "Event-type: audio-stream-stats\n"; + ostr << "Id: " << daemon->updateAudioStreamId(stream) << "\n"; + ostr << "Type: "; + if (stats->type == LINPHONE_CALL_STATS_AUDIO) { + ostr << "Audio"; + } else { + ostr << "Video"; + } + ostr << "\n"; + } else { + prefix = ((stats->type == LINPHONE_CALL_STATS_AUDIO) ? "Audio-" : "Video-"); + } + + printCallStatsHelper(ostr, stats, prefix); + + setBody(ostr.str().c_str()); +} + PayloadTypeResponse::PayloadTypeResponse(LinphoneCore *core, const PayloadType *payloadType, int index, const string &prefix, bool enabled_status) { ostringstream ostr; if (payloadType != NULL) { @@ -336,27 +368,36 @@ LinphoneProxyConfig *Daemon::findProxy(int id) { } int Daemon::updateAudioStreamId(AudioStream *audio_stream) { - for (std::map::iterator it = mAudioStreams.begin(); it != mAudioStreams.end(); ++it) { - if (it->second == audio_stream) + for (std::map::iterator it = mAudioStreams.begin(); it != mAudioStreams.end(); ++it) { + if (it->second->stream == audio_stream) return it->first; } ++mProxyIds; - mAudioStreams.insert(std::pair(mProxyIds, audio_stream)); + mAudioStreams.insert(make_pair(mProxyIds, new AudioStreamAndOther(audio_stream))); return mProxyIds; } -AudioStream *Daemon::findAudioStream(int id) { - std::map::iterator it = mAudioStreams.find(id); +AudioStreamAndOther *Daemon::findAudioStreamAndOther(int id) { + std::map::iterator it = mAudioStreams.find(id); if (it != mAudioStreams.end()) return it->second; return NULL; } -void Daemon::removeAudioStream(int id) { - std::map::iterator it = mAudioStreams.find(id); +AudioStream *Daemon::findAudioStream(int id) { + std::map::iterator it = mAudioStreams.find(id); if (it != mAudioStreams.end()) + return it->second->stream; + return NULL; +} + +void Daemon::removeAudioStream(int id) { + std::map::iterator it = mAudioStreams.find(id); + if (it != mAudioStreams.end()) { mAudioStreams.erase(it); + delete(it->second); + } } void Daemon::initCommands() { @@ -379,6 +420,7 @@ void Daemon::initCommands() { mCommands.push_back(new AudioCodecSetCommand()); mCommands.push_back(new AudioStreamStartCommand()); mCommands.push_back(new AudioStreamStopCommand()); + mCommands.push_back(new AudioStreamStatsCommand()); mCommands.push_back(new MSFilterAddFmtpCommand()); mCommands.push_back(new PtimeCommand()); mCommands.push_back(new IPv6Command()); @@ -452,8 +494,24 @@ void Daemon::dtmfReceived(LinphoneCore *lc, LinphoneCall *call, int dtmf) { app->dtmfReceived(call, dtmf); } +void Daemon::iterateStreamStats() { + for (std::map::iterator it = mAudioStreams.begin(); it != mAudioStreams.end(); ++it) { + OrtpEvent *ev; + while (it->second->queue && (NULL != (ev=ortp_ev_queue_get(it->second->queue)))){ + OrtpEventType evt=ortp_event_get_type(ev); + if (evt == ORTP_EVENT_RTCP_PACKET_RECEIVED || evt == ORTP_EVENT_RTCP_PACKET_EMITTED) { + linphone_call_stream_stats_hack(&it->second->stream->ms, &it->second->stats, ev); + if (mUseStatsEvents) mEventQueue.push(new AudioStreamStatsResponse(this, + it->second->stream, &it->second->stats, true)); + } + ortp_event_destroy(ev); + } + } +} + void Daemon::iterate() { linphone_core_iterate(mLc); + iterateStreamStats(); if (mChildFd == -1) { if (!mEventQueue.empty()) { Response *r = mEventQueue.front(); @@ -707,8 +765,8 @@ void Daemon::enableLSD(bool enabled) { Daemon::~Daemon() { uninitCommands(); - for (std::map::iterator it = mAudioStreams.begin(); it != mAudioStreams.end(); ++it) { - audio_stream_stop(it->second); + for (std::map::iterator it = mAudioStreams.begin(); it != mAudioStreams.end(); ++it) { + audio_stream_stop(it->second->stream); } enableLSD(false); diff --git a/daemon/daemon.h b/daemon/daemon.h index e640bfc0b..3cdee3c64 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -126,25 +126,27 @@ private: class EventResponse: public Response { public: EventResponse(Daemon *daemon, LinphoneCall *call, LinphoneCallState state); -private: }; class CallStatsResponse: public Response { public: CallStatsResponse(Daemon *daemon, LinphoneCall *call, const LinphoneCallStats *stats, bool unique); -private: +}; + +class AudioStreamStatsResponse: public Response { +public: + AudioStreamStatsResponse(Daemon *daemon, AudioStream *stream, + const LinphoneCallStats *stats, bool event); }; class DtmfResponse: public Response { public: DtmfResponse(Daemon *daemon, LinphoneCall *call, int dtmf); -private: }; class PayloadTypeResponse: public Response { public: PayloadTypeResponse(LinphoneCore *core, const PayloadType *payloadType, int index = -1, const std::string &prefix = std::string(), bool enabled_status = true); -private: }; class PayloadTypeParser { @@ -159,6 +161,21 @@ private: int mPayloadTypeNumber; }; +struct AudioStreamAndOther { + AudioStream *stream; + OrtpEvQueue *queue; + LinphoneCallStats stats; + AudioStreamAndOther(AudioStream *as) : stream(as) { + queue = ortp_ev_queue_new(); + rtp_session_register_event_queue(as->ms.session, queue); + memset(&stats, 0, sizeof(stats)); + } + ~AudioStreamAndOther() { + rtp_session_unregister_event_queue(stream->ms.session, queue); + ortp_ev_queue_destroy(queue); + } +}; + class Daemon { friend class DaemonCommand; public: @@ -174,6 +191,7 @@ public: LinphoneCall *findCall(int id); LinphoneProxyConfig *findProxy(int id); AudioStream *findAudioStream(int id); + AudioStreamAndOther *findAudioStreamAndOther(int id); void removeAudioStream(int id); bool pullEvent(); int updateCallId(LinphoneCall *call); @@ -196,10 +214,12 @@ private: char *readLine(const char *); char *readPipe(char *buffer, int buflen); void iterate(); + void iterateStreamStats(); void startThread(); void stopThread(); void initCommands(); void uninitCommands(); + void iterateStreamStats(LinphoneCore *lc); LinphoneCore *mLc; LinphoneSoundDaemon *mLSD; std::list mCommands; @@ -216,7 +236,7 @@ private: ms_thread_t mThread; ms_mutex_t mMutex; static const int sLineSize = 512; - std::map mAudioStreams; + std::map mAudioStreams; }; #endif //DAEMON_H_ diff --git a/mediastreamer2 b/mediastreamer2 index aebfe443e..43b68b22c 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit aebfe443e91aa1e1c1e32b2410cc1d9a5db629c1 +Subproject commit 43b68b22cf8d0d1b0b5a064516d35899e5527358 diff --git a/oRTP b/oRTP index 49b16793b..7c2805c2e 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit 49b16793b9ef8251a4c42434b57387c6e3c6d251 +Subproject commit 7c2805c2e2c1edadf9e9efe313d98f06c4c3614a