blob: 2b584196981ffbacab1b6afd75ecef60a096007c [file] [log] [blame]
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001/*
kjellander65c7f672016-02-12 00:05:01 -08002 * Copyright 2011 The WebRTC project authors. All Rights Reserved.
henrike@webrtc.org28e20752013-07-10 00:45:36 +00003 *
kjellander65c7f672016-02-12 00:05:01 -08004 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
henrike@webrtc.org28e20752013-07-10 00:45:36 +00009 */
10
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020011#include "pc/currentspeakermonitor.h"
henrike@webrtc.org28e20752013-07-10 00:45:36 +000012
terelius8c011e52016-04-26 05:28:11 -070013#include <vector>
14
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020015#include "media/base/streamparams.h"
16#include "pc/audiomonitor.h"
17#include "rtc_base/logging.h"
Niels Möllerf120cba2018-01-30 09:33:03 +010018#include "rtc_base/timeutils.h"
henrike@webrtc.org28e20752013-07-10 00:45:36 +000019
20namespace cricket {
21
22namespace {
23const int kMaxAudioLevel = 9;
24// To avoid overswitching, we disable switching for a period of time after a
25// switch is done.
26const int kDefaultMinTimeBetweenSwitches = 1000;
27}
28
buildbot@webrtc.orgca272362014-05-08 23:10:23 +000029CurrentSpeakerMonitor::CurrentSpeakerMonitor(
deadbeefd59daf82015-10-14 15:02:44 -070030 AudioSourceContext* audio_source_context)
henrike@webrtc.org28e20752013-07-10 00:45:36 +000031 : started_(false),
buildbot@webrtc.orgca272362014-05-08 23:10:23 +000032 audio_source_context_(audio_source_context),
henrike@webrtc.org28e20752013-07-10 00:45:36 +000033 current_speaker_ssrc_(0),
34 earliest_permitted_switch_time_(0),
deadbeefd59daf82015-10-14 15:02:44 -070035 min_time_between_switches_(kDefaultMinTimeBetweenSwitches) {}
henrike@webrtc.org28e20752013-07-10 00:45:36 +000036
37CurrentSpeakerMonitor::~CurrentSpeakerMonitor() {
38 Stop();
39}
40
41void CurrentSpeakerMonitor::Start() {
42 if (!started_) {
buildbot@webrtc.orgca272362014-05-08 23:10:23 +000043 audio_source_context_->SignalAudioMonitor.connect(
henrike@webrtc.org28e20752013-07-10 00:45:36 +000044 this, &CurrentSpeakerMonitor::OnAudioMonitor);
buildbot@webrtc.orgca272362014-05-08 23:10:23 +000045 audio_source_context_->SignalMediaStreamsUpdate.connect(
henrike@webrtc.org28e20752013-07-10 00:45:36 +000046 this, &CurrentSpeakerMonitor::OnMediaStreamsUpdate);
buildbot@webrtc.org49a6a272014-05-21 00:24:54 +000047 audio_source_context_->SignalMediaStreamsReset.connect(
48 this, &CurrentSpeakerMonitor::OnMediaStreamsReset);
henrike@webrtc.org28e20752013-07-10 00:45:36 +000049
50 started_ = true;
51 }
52}
53
54void CurrentSpeakerMonitor::Stop() {
55 if (started_) {
buildbot@webrtc.orgca272362014-05-08 23:10:23 +000056 audio_source_context_->SignalAudioMonitor.disconnect(this);
57 audio_source_context_->SignalMediaStreamsUpdate.disconnect(this);
henrike@webrtc.org28e20752013-07-10 00:45:36 +000058
59 started_ = false;
60 ssrc_to_speaking_state_map_.clear();
61 current_speaker_ssrc_ = 0;
62 earliest_permitted_switch_time_ = 0;
63 }
64}
65
66void CurrentSpeakerMonitor::set_min_time_between_switches(
Honghai Zhang82d78622016-05-06 11:29:15 -070067 int min_time_between_switches) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +000068 min_time_between_switches_ = min_time_between_switches;
69}
70
buildbot@webrtc.orgca272362014-05-08 23:10:23 +000071void CurrentSpeakerMonitor::OnAudioMonitor(
72 AudioSourceContext* audio_source_context, const AudioInfo& info) {
Peter Boström0c4e06b2015-10-07 12:23:21 +020073 std::map<uint32_t, int> active_ssrc_to_level_map;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000074 cricket::AudioInfo::StreamList::const_iterator stream_list_it;
75 for (stream_list_it = info.active_streams.begin();
76 stream_list_it != info.active_streams.end(); ++stream_list_it) {
Peter Boström0c4e06b2015-10-07 12:23:21 +020077 uint32_t ssrc = stream_list_it->first;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000078 active_ssrc_to_level_map[ssrc] = stream_list_it->second;
79
80 // It's possible we haven't yet added this source to our map. If so,
81 // add it now with a "not speaking" state.
82 if (ssrc_to_speaking_state_map_.find(ssrc) ==
83 ssrc_to_speaking_state_map_.end()) {
84 ssrc_to_speaking_state_map_[ssrc] = SS_NOT_SPEAKING;
85 }
86 }
87
88 int max_level = 0;
Peter Boström0c4e06b2015-10-07 12:23:21 +020089 uint32_t loudest_speaker_ssrc = 0;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000090
91 // Update the speaking states of all participants based on the new audio
92 // level information. Also retain loudest speaker.
Peter Boström0c4e06b2015-10-07 12:23:21 +020093 std::map<uint32_t, SpeakingState>::iterator state_it;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000094 for (state_it = ssrc_to_speaking_state_map_.begin();
95 state_it != ssrc_to_speaking_state_map_.end(); ++state_it) {
96 bool is_previous_speaker = current_speaker_ssrc_ == state_it->first;
97
98 // This uses a state machine in order to gradually identify
99 // members as having started or stopped speaking. Matches the
100 // algorithm used by the hangouts js code.
101
Peter Boström0c4e06b2015-10-07 12:23:21 +0200102 std::map<uint32_t, int>::const_iterator level_it =
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000103 active_ssrc_to_level_map.find(state_it->first);
104 // Note that the stream map only contains streams with non-zero audio
105 // levels.
106 int level = (level_it != active_ssrc_to_level_map.end()) ?
107 level_it->second : 0;
108 switch (state_it->second) {
109 case SS_NOT_SPEAKING:
110 if (level > 0) {
111 // Reset level because we don't think they're really speaking.
112 level = 0;
113 state_it->second = SS_MIGHT_BE_SPEAKING;
114 } else {
115 // State unchanged.
116 }
117 break;
118 case SS_MIGHT_BE_SPEAKING:
119 if (level > 0) {
120 state_it->second = SS_SPEAKING;
121 } else {
122 state_it->second = SS_NOT_SPEAKING;
123 }
124 break;
125 case SS_SPEAKING:
126 if (level > 0) {
127 // State unchanged.
128 } else {
129 state_it->second = SS_WAS_SPEAKING_RECENTLY1;
130 if (is_previous_speaker) {
131 // Assume this is an inter-word silence and assign him the highest
132 // volume.
133 level = kMaxAudioLevel;
134 }
135 }
136 break;
137 case SS_WAS_SPEAKING_RECENTLY1:
138 if (level > 0) {
139 state_it->second = SS_SPEAKING;
140 } else {
141 state_it->second = SS_WAS_SPEAKING_RECENTLY2;
142 if (is_previous_speaker) {
143 // Assume this is an inter-word silence and assign him the highest
144 // volume.
145 level = kMaxAudioLevel;
146 }
147 }
148 break;
149 case SS_WAS_SPEAKING_RECENTLY2:
150 if (level > 0) {
151 state_it->second = SS_SPEAKING;
152 } else {
153 state_it->second = SS_NOT_SPEAKING;
154 }
155 break;
156 }
157
158 if (level > max_level) {
159 loudest_speaker_ssrc = state_it->first;
160 max_level = level;
161 } else if (level > 0 && level == max_level && is_previous_speaker) {
162 // Favor continuity of loudest speakers if audio levels are equal.
163 loudest_speaker_ssrc = state_it->first;
164 }
165 }
166
167 // We avoid over-switching by disabling switching for a period of time after
168 // a switch is done.
Honghai Zhang82d78622016-05-06 11:29:15 -0700169 int64_t now = rtc::TimeMillis();
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000170 if (earliest_permitted_switch_time_ <= now &&
171 current_speaker_ssrc_ != loudest_speaker_ssrc) {
172 current_speaker_ssrc_ = loudest_speaker_ssrc;
Mirko Bonadei675513b2017-11-09 11:09:25 +0100173 RTC_LOG(LS_INFO) << "Current speaker changed to " << current_speaker_ssrc_;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000174 earliest_permitted_switch_time_ = now + min_time_between_switches_;
175 SignalUpdate(this, current_speaker_ssrc_);
176 }
177}
178
buildbot@webrtc.orgca272362014-05-08 23:10:23 +0000179void CurrentSpeakerMonitor::OnMediaStreamsUpdate(
deadbeefd59daf82015-10-14 15:02:44 -0700180 AudioSourceContext* audio_source_context,
181 const MediaStreams& added,
182 const MediaStreams& removed) {
183 if (audio_source_context == audio_source_context_) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000184 // Update the speaking state map based on added and removed streams.
185 for (std::vector<cricket::StreamParams>::const_iterator
buildbot@webrtc.org49a6a272014-05-21 00:24:54 +0000186 it = removed.audio().begin(); it != removed.audio().end(); ++it) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000187 ssrc_to_speaking_state_map_.erase(it->first_ssrc());
188 }
189
190 for (std::vector<cricket::StreamParams>::const_iterator
buildbot@webrtc.org49a6a272014-05-21 00:24:54 +0000191 it = added.audio().begin(); it != added.audio().end(); ++it) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000192 ssrc_to_speaking_state_map_[it->first_ssrc()] = SS_NOT_SPEAKING;
193 }
194 }
195}
196
buildbot@webrtc.org49a6a272014-05-21 00:24:54 +0000197void CurrentSpeakerMonitor::OnMediaStreamsReset(
deadbeefd59daf82015-10-14 15:02:44 -0700198 AudioSourceContext* audio_source_context) {
199 if (audio_source_context == audio_source_context_) {
buildbot@webrtc.org49a6a272014-05-21 00:24:54 +0000200 ssrc_to_speaking_state_map_.clear();
201 }
202}
203
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000204} // namespace cricket