aboutsummaryrefslogtreecommitdiff
path: root/webrtc/audio
diff options
context:
space:
mode:
Diffstat (limited to 'webrtc/audio')
-rw-r--r--webrtc/audio/BUILD.gn5
-rw-r--r--webrtc/audio/audio_receive_stream.cc244
-rw-r--r--webrtc/audio/audio_receive_stream.h27
-rw-r--r--webrtc/audio/audio_receive_stream_unittest.cc364
-rw-r--r--webrtc/audio/audio_send_stream.cc185
-rw-r--r--webrtc/audio/audio_send_stream.h23
-rw-r--r--webrtc/audio/audio_send_stream_unittest.cc243
-rw-r--r--webrtc/audio/audio_sink.h53
-rw-r--r--webrtc/audio/audio_state.cc79
-rw-r--r--webrtc/audio/audio_state.h61
-rw-r--r--webrtc/audio/audio_state_unittest.cc80
-rw-r--r--webrtc/audio/webrtc_audio.gypi3
12 files changed, 1031 insertions, 336 deletions
diff --git a/webrtc/audio/BUILD.gn b/webrtc/audio/BUILD.gn
index d5061db9dc..5a9902eac1 100644
--- a/webrtc/audio/BUILD.gn
+++ b/webrtc/audio/BUILD.gn
@@ -14,6 +14,9 @@ source_set("audio") {
"audio_receive_stream.h",
"audio_send_stream.cc",
"audio_send_stream.h",
+ "audio_sink.h",
+ "audio_state.cc",
+ "audio_state.h",
"conversion.h",
"scoped_voe_interface.h",
]
@@ -29,7 +32,7 @@ source_set("audio") {
deps = [
"..:webrtc_common",
- "../voice_engine",
"../system_wrappers",
+ "../voice_engine",
]
}
diff --git a/webrtc/audio/audio_receive_stream.cc b/webrtc/audio/audio_receive_stream.cc
index 34197c3ff7..64d008326d 100644
--- a/webrtc/audio/audio_receive_stream.cc
+++ b/webrtc/audio/audio_receive_stream.cc
@@ -11,20 +11,41 @@
#include "webrtc/audio/audio_receive_stream.h"
#include <string>
+#include <utility>
+#include "webrtc/audio/audio_sink.h"
+#include "webrtc/audio/audio_state.h"
#include "webrtc/audio/conversion.h"
#include "webrtc/base/checks.h"
#include "webrtc/base/logging.h"
+#include "webrtc/call/congestion_controller.h"
#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
#include "webrtc/system_wrappers/include/tick_util.h"
+#include "webrtc/voice_engine/channel_proxy.h"
#include "webrtc/voice_engine/include/voe_base.h"
#include "webrtc/voice_engine/include/voe_codec.h"
#include "webrtc/voice_engine/include/voe_neteq_stats.h"
#include "webrtc/voice_engine/include/voe_rtp_rtcp.h"
#include "webrtc/voice_engine/include/voe_video_sync.h"
#include "webrtc/voice_engine/include/voe_volume_control.h"
+#include "webrtc/voice_engine/voice_engine_impl.h"
namespace webrtc {
+namespace {
+
+bool UseSendSideBwe(const webrtc::AudioReceiveStream::Config& config) {
+ if (!config.rtp.transport_cc) {
+ return false;
+ }
+ for (const auto& extension : config.rtp.extensions) {
+ if (extension.name == RtpExtension::kTransportSequenceNumber) {
+ return true;
+ }
+ }
+ return false;
+}
+} // namespace
+
std::string AudioReceiveStream::Config::Rtp::ToString() const {
std::stringstream ss;
ss << "{remote_ssrc: " << remote_ssrc;
@@ -60,120 +81,62 @@ std::string AudioReceiveStream::Config::ToString() const {
namespace internal {
AudioReceiveStream::AudioReceiveStream(
- RemoteBitrateEstimator* remote_bitrate_estimator,
- const webrtc::AudioReceiveStream::Config& config,
- VoiceEngine* voice_engine)
- : remote_bitrate_estimator_(remote_bitrate_estimator),
- config_(config),
- voice_engine_(voice_engine),
- voe_base_(voice_engine),
+ CongestionController* congestion_controller,
+ const webrtc::AudioReceiveStream::Config& config,
+ const rtc::scoped_refptr<webrtc::AudioState>& audio_state)
+ : config_(config),
+ audio_state_(audio_state),
rtp_header_parser_(RtpHeaderParser::Create()) {
LOG(LS_INFO) << "AudioReceiveStream: " << config_.ToString();
- RTC_DCHECK(config.voe_channel_id != -1);
- RTC_DCHECK(remote_bitrate_estimator_ != nullptr);
- RTC_DCHECK(voice_engine_ != nullptr);
- RTC_DCHECK(rtp_header_parser_ != nullptr);
- for (const auto& ext : config.rtp.extensions) {
- // One-byte-extension local identifiers are in the range 1-14 inclusive.
- RTC_DCHECK_GE(ext.id, 1);
- RTC_DCHECK_LE(ext.id, 14);
- if (ext.name == RtpExtension::kAudioLevel) {
- RTC_CHECK(rtp_header_parser_->RegisterRtpHeaderExtension(
- kRtpExtensionAudioLevel, ext.id));
- } else if (ext.name == RtpExtension::kAbsSendTime) {
- RTC_CHECK(rtp_header_parser_->RegisterRtpHeaderExtension(
- kRtpExtensionAbsoluteSendTime, ext.id));
- } else if (ext.name == RtpExtension::kTransportSequenceNumber) {
- RTC_CHECK(rtp_header_parser_->RegisterRtpHeaderExtension(
- kRtpExtensionTransportSequenceNumber, ext.id));
+ RTC_DCHECK_NE(config_.voe_channel_id, -1);
+ RTC_DCHECK(audio_state_.get());
+ RTC_DCHECK(congestion_controller);
+ RTC_DCHECK(rtp_header_parser_);
+
+ VoiceEngineImpl* voe_impl = static_cast<VoiceEngineImpl*>(voice_engine());
+ channel_proxy_ = voe_impl->GetChannelProxy(config_.voe_channel_id);
+ channel_proxy_->SetLocalSSRC(config.rtp.local_ssrc);
+ for (const auto& extension : config.rtp.extensions) {
+ if (extension.name == RtpExtension::kAudioLevel) {
+ channel_proxy_->SetReceiveAudioLevelIndicationStatus(true, extension.id);
+ bool registered = rtp_header_parser_->RegisterRtpHeaderExtension(
+ kRtpExtensionAudioLevel, extension.id);
+ RTC_DCHECK(registered);
+ } else if (extension.name == RtpExtension::kAbsSendTime) {
+ channel_proxy_->SetReceiveAbsoluteSenderTimeStatus(true, extension.id);
+ bool registered = rtp_header_parser_->RegisterRtpHeaderExtension(
+ kRtpExtensionAbsoluteSendTime, extension.id);
+ RTC_DCHECK(registered);
+ } else if (extension.name == RtpExtension::kTransportSequenceNumber) {
+ bool registered = rtp_header_parser_->RegisterRtpHeaderExtension(
+ kRtpExtensionTransportSequenceNumber, extension.id);
+ RTC_DCHECK(registered);
} else {
RTC_NOTREACHED() << "Unsupported RTP extension.";
}
}
+ // Configure bandwidth estimation.
+ channel_proxy_->SetCongestionControlObjects(
+ nullptr, nullptr, congestion_controller->packet_router());
+ if (config.combined_audio_video_bwe) {
+ if (UseSendSideBwe(config)) {
+ remote_bitrate_estimator_ =
+ congestion_controller->GetRemoteBitrateEstimator(true);
+ } else {
+ remote_bitrate_estimator_ =
+ congestion_controller->GetRemoteBitrateEstimator(false);
+ }
+ RTC_DCHECK(remote_bitrate_estimator_);
+ }
}
AudioReceiveStream::~AudioReceiveStream() {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
LOG(LS_INFO) << "~AudioReceiveStream: " << config_.ToString();
-}
-
-webrtc::AudioReceiveStream::Stats AudioReceiveStream::GetStats() const {
- RTC_DCHECK(thread_checker_.CalledOnValidThread());
- webrtc::AudioReceiveStream::Stats stats;
- stats.remote_ssrc = config_.rtp.remote_ssrc;
- ScopedVoEInterface<VoECodec> codec(voice_engine_);
- ScopedVoEInterface<VoENetEqStats> neteq(voice_engine_);
- ScopedVoEInterface<VoERTP_RTCP> rtp(voice_engine_);
- ScopedVoEInterface<VoEVideoSync> sync(voice_engine_);
- ScopedVoEInterface<VoEVolumeControl> volume(voice_engine_);
- unsigned int ssrc = 0;
- webrtc::CallStatistics call_stats = {0};
- webrtc::CodecInst codec_inst = {0};
- // Only collect stats if we have seen some traffic with the SSRC.
- if (rtp->GetRemoteSSRC(config_.voe_channel_id, ssrc) == -1 ||
- rtp->GetRTCPStatistics(config_.voe_channel_id, call_stats) == -1 ||
- codec->GetRecCodec(config_.voe_channel_id, codec_inst) == -1) {
- return stats;
+ channel_proxy_->SetCongestionControlObjects(nullptr, nullptr, nullptr);
+ if (remote_bitrate_estimator_) {
+ remote_bitrate_estimator_->RemoveStream(config_.rtp.remote_ssrc);
}
-
- stats.bytes_rcvd = call_stats.bytesReceived;
- stats.packets_rcvd = call_stats.packetsReceived;
- stats.packets_lost = call_stats.cumulativeLost;
- stats.fraction_lost = Q8ToFloat(call_stats.fractionLost);
- if (codec_inst.pltype != -1) {
- stats.codec_name = codec_inst.plname;
- }
- stats.ext_seqnum = call_stats.extendedMax;
- if (codec_inst.plfreq / 1000 > 0) {
- stats.jitter_ms = call_stats.jitterSamples / (codec_inst.plfreq / 1000);
- }
- {
- int jitter_buffer_delay_ms = 0;
- int playout_buffer_delay_ms = 0;
- sync->GetDelayEstimate(config_.voe_channel_id, &jitter_buffer_delay_ms,
- &playout_buffer_delay_ms);
- stats.delay_estimate_ms =
- jitter_buffer_delay_ms + playout_buffer_delay_ms;
- }
- {
- unsigned int level = 0;
- if (volume->GetSpeechOutputLevelFullRange(config_.voe_channel_id, level)
- != -1) {
- stats.audio_level = static_cast<int32_t>(level);
- }
- }
-
- webrtc::NetworkStatistics ns = {0};
- if (neteq->GetNetworkStatistics(config_.voe_channel_id, ns) != -1) {
- // Get jitter buffer and total delay (alg + jitter + playout) stats.
- stats.jitter_buffer_ms = ns.currentBufferSize;
- stats.jitter_buffer_preferred_ms = ns.preferredBufferSize;
- stats.expand_rate = Q14ToFloat(ns.currentExpandRate);
- stats.speech_expand_rate = Q14ToFloat(ns.currentSpeechExpandRate);
- stats.secondary_decoded_rate = Q14ToFloat(ns.currentSecondaryDecodedRate);
- stats.accelerate_rate = Q14ToFloat(ns.currentAccelerateRate);
- stats.preemptive_expand_rate = Q14ToFloat(ns.currentPreemptiveRate);
- }
-
- webrtc::AudioDecodingCallStats ds;
- if (neteq->GetDecodingCallStatistics(config_.voe_channel_id, &ds) != -1) {
- stats.decoding_calls_to_silence_generator =
- ds.calls_to_silence_generator;
- stats.decoding_calls_to_neteq = ds.calls_to_neteq;
- stats.decoding_normal = ds.decoded_normal;
- stats.decoding_plc = ds.decoded_plc;
- stats.decoding_cng = ds.decoded_cng;
- stats.decoding_plc_cng = ds.decoded_plc_cng;
- }
-
- stats.capture_start_ntp_time_ms = call_stats.capture_start_ntp_time_ms_;
-
- return stats;
-}
-
-const webrtc::AudioReceiveStream::Config& AudioReceiveStream::config() const {
- RTC_DCHECK(thread_checker_.CalledOnValidThread());
- return config_;
}
void AudioReceiveStream::Start() {
@@ -204,15 +167,16 @@ bool AudioReceiveStream::DeliverRtp(const uint8_t* packet,
// thread. Then this check can be enabled.
// RTC_DCHECK(!thread_checker_.CalledOnValidThread());
RTPHeader header;
-
if (!rtp_header_parser_->Parse(packet, length, &header)) {
return false;
}
- // Only forward if the parsed header has absolute sender time. RTP timestamps
- // may have different rates for audio and video and shouldn't be mixed.
- if (config_.combined_audio_video_bwe &&
- header.extension.hasAbsoluteSendTime) {
+ // Only forward if the parsed header has one of the headers necessary for
+ // bandwidth estimation. RTP timestamps has different rates for audio and
+ // video and shouldn't be mixed.
+ if (remote_bitrate_estimator_ &&
+ (header.extension.hasAbsoluteSendTime ||
+ header.extension.hasTransportSequenceNumber)) {
int64_t arrival_time_ms = TickTime::MillisecondTimestamp();
if (packet_time.timestamp >= 0)
arrival_time_ms = (packet_time.timestamp + 500) / 1000;
@@ -222,5 +186,71 @@ bool AudioReceiveStream::DeliverRtp(const uint8_t* packet,
}
return true;
}
+
+webrtc::AudioReceiveStream::Stats AudioReceiveStream::GetStats() const {
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+ webrtc::AudioReceiveStream::Stats stats;
+ stats.remote_ssrc = config_.rtp.remote_ssrc;
+ ScopedVoEInterface<VoECodec> codec(voice_engine());
+
+ webrtc::CallStatistics call_stats = channel_proxy_->GetRTCPStatistics();
+ webrtc::CodecInst codec_inst = {0};
+ if (codec->GetRecCodec(config_.voe_channel_id, codec_inst) == -1) {
+ return stats;
+ }
+
+ stats.bytes_rcvd = call_stats.bytesReceived;
+ stats.packets_rcvd = call_stats.packetsReceived;
+ stats.packets_lost = call_stats.cumulativeLost;
+ stats.fraction_lost = Q8ToFloat(call_stats.fractionLost);
+ stats.capture_start_ntp_time_ms = call_stats.capture_start_ntp_time_ms_;
+ if (codec_inst.pltype != -1) {
+ stats.codec_name = codec_inst.plname;
+ }
+ stats.ext_seqnum = call_stats.extendedMax;
+ if (codec_inst.plfreq / 1000 > 0) {
+ stats.jitter_ms = call_stats.jitterSamples / (codec_inst.plfreq / 1000);
+ }
+ stats.delay_estimate_ms = channel_proxy_->GetDelayEstimate();
+ stats.audio_level = channel_proxy_->GetSpeechOutputLevelFullRange();
+
+ // Get jitter buffer and total delay (alg + jitter + playout) stats.
+ auto ns = channel_proxy_->GetNetworkStatistics();
+ stats.jitter_buffer_ms = ns.currentBufferSize;
+ stats.jitter_buffer_preferred_ms = ns.preferredBufferSize;
+ stats.expand_rate = Q14ToFloat(ns.currentExpandRate);
+ stats.speech_expand_rate = Q14ToFloat(ns.currentSpeechExpandRate);
+ stats.secondary_decoded_rate = Q14ToFloat(ns.currentSecondaryDecodedRate);
+ stats.accelerate_rate = Q14ToFloat(ns.currentAccelerateRate);
+ stats.preemptive_expand_rate = Q14ToFloat(ns.currentPreemptiveRate);
+
+ auto ds = channel_proxy_->GetDecodingCallStatistics();
+ stats.decoding_calls_to_silence_generator = ds.calls_to_silence_generator;
+ stats.decoding_calls_to_neteq = ds.calls_to_neteq;
+ stats.decoding_normal = ds.decoded_normal;
+ stats.decoding_plc = ds.decoded_plc;
+ stats.decoding_cng = ds.decoded_cng;
+ stats.decoding_plc_cng = ds.decoded_plc_cng;
+
+ return stats;
+}
+
+void AudioReceiveStream::SetSink(rtc::scoped_ptr<AudioSinkInterface> sink) {
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+ channel_proxy_->SetSink(std::move(sink));
+}
+
+const webrtc::AudioReceiveStream::Config& AudioReceiveStream::config() const {
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+ return config_;
+}
+
+VoiceEngine* AudioReceiveStream::voice_engine() const {
+ internal::AudioState* audio_state =
+ static_cast<internal::AudioState*>(audio_state_.get());
+ VoiceEngine* voice_engine = audio_state->voice_engine();
+ RTC_DCHECK(voice_engine);
+ return voice_engine;
+}
} // namespace internal
} // namespace webrtc
diff --git a/webrtc/audio/audio_receive_stream.h b/webrtc/audio/audio_receive_stream.h
index 5d02b0e2ae..4940c6a64c 100644
--- a/webrtc/audio/audio_receive_stream.h
+++ b/webrtc/audio/audio_receive_stream.h
@@ -12,23 +12,25 @@
#define WEBRTC_AUDIO_AUDIO_RECEIVE_STREAM_H_
#include "webrtc/audio_receive_stream.h"
-#include "webrtc/audio/scoped_voe_interface.h"
+#include "webrtc/audio_state.h"
#include "webrtc/base/thread_checker.h"
-#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h"
-#include "webrtc/voice_engine/include/voe_base.h"
+#include "webrtc/modules/rtp_rtcp/include/rtp_header_parser.h"
namespace webrtc {
-
+class CongestionController;
class RemoteBitrateEstimator;
-class VoiceEngine;
+
+namespace voe {
+class ChannelProxy;
+} // namespace voe
namespace internal {
class AudioReceiveStream final : public webrtc::AudioReceiveStream {
public:
- AudioReceiveStream(RemoteBitrateEstimator* remote_bitrate_estimator,
+ AudioReceiveStream(CongestionController* congestion_controller,
const webrtc::AudioReceiveStream::Config& config,
- VoiceEngine* voice_engine);
+ const rtc::scoped_refptr<webrtc::AudioState>& audio_state);
~AudioReceiveStream() override;
// webrtc::ReceiveStream implementation.
@@ -43,16 +45,19 @@ class AudioReceiveStream final : public webrtc::AudioReceiveStream {
// webrtc::AudioReceiveStream implementation.
webrtc::AudioReceiveStream::Stats GetStats() const override;
+ void SetSink(rtc::scoped_ptr<AudioSinkInterface> sink) override;
+
const webrtc::AudioReceiveStream::Config& config() const;
private:
+ VoiceEngine* voice_engine() const;
+
rtc::ThreadChecker thread_checker_;
- RemoteBitrateEstimator* const remote_bitrate_estimator_;
+ RemoteBitrateEstimator* remote_bitrate_estimator_ = nullptr;
const webrtc::AudioReceiveStream::Config config_;
- VoiceEngine* voice_engine_;
- // We hold one interface pointer to the VoE to make sure it is kept alive.
- ScopedVoEInterface<VoEBase> voe_base_;
+ rtc::scoped_refptr<webrtc::AudioState> audio_state_;
rtc::scoped_ptr<RtpHeaderParser> rtp_header_parser_;
+ rtc::scoped_ptr<voe::ChannelProxy> channel_proxy_;
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(AudioReceiveStream);
};
diff --git a/webrtc/audio/audio_receive_stream_unittest.cc b/webrtc/audio/audio_receive_stream_unittest.cc
index 4e267f1738..eb008b3045 100644
--- a/webrtc/audio/audio_receive_stream_unittest.cc
+++ b/webrtc/audio/audio_receive_stream_unittest.cc
@@ -8,154 +8,320 @@
* be found in the AUTHORS file in the root of the source tree.
*/
+#include <string>
+
#include "testing/gtest/include/gtest/gtest.h"
#include "webrtc/audio/audio_receive_stream.h"
#include "webrtc/audio/conversion.h"
+#include "webrtc/call/mock/mock_congestion_controller.h"
+#include "webrtc/modules/bitrate_controller/include/mock/mock_bitrate_controller.h"
+#include "webrtc/modules/pacing/packet_router.h"
#include "webrtc/modules/remote_bitrate_estimator/include/mock/mock_remote_bitrate_estimator.h"
#include "webrtc/modules/rtp_rtcp/source/byte_io.h"
-#include "webrtc/test/fake_voice_engine.h"
+#include "webrtc/modules/utility/include/mock/mock_process_thread.h"
+#include "webrtc/system_wrappers/include/clock.h"
+#include "webrtc/test/mock_voe_channel_proxy.h"
+#include "webrtc/test/mock_voice_engine.h"
+#include "webrtc/video/call_stats.h"
+namespace webrtc {
+namespace test {
namespace {
-using webrtc::ByteWriter;
+using testing::_;
+using testing::Return;
-const size_t kAbsoluteSendTimeLength = 4;
+AudioDecodingCallStats MakeAudioDecodeStatsForTest() {
+ AudioDecodingCallStats audio_decode_stats;
+ audio_decode_stats.calls_to_silence_generator = 234;
+ audio_decode_stats.calls_to_neteq = 567;
+ audio_decode_stats.decoded_normal = 890;
+ audio_decode_stats.decoded_plc = 123;
+ audio_decode_stats.decoded_cng = 456;
+ audio_decode_stats.decoded_plc_cng = 789;
+ return audio_decode_stats;
+}
-void BuildAbsoluteSendTimeExtension(uint8_t* buffer,
- int id,
- uint32_t abs_send_time) {
- const size_t kRtpOneByteHeaderLength = 4;
- const uint16_t kRtpOneByteHeaderExtensionId = 0xBEDE;
- ByteWriter<uint16_t>::WriteBigEndian(buffer, kRtpOneByteHeaderExtensionId);
+const int kChannelId = 2;
+const uint32_t kRemoteSsrc = 1234;
+const uint32_t kLocalSsrc = 5678;
+const size_t kOneByteExtensionHeaderLength = 4;
+const size_t kOneByteExtensionLength = 4;
+const int kAbsSendTimeId = 2;
+const int kAudioLevelId = 3;
+const int kTransportSequenceNumberId = 4;
+const int kJitterBufferDelay = -7;
+const int kPlayoutBufferDelay = 302;
+const unsigned int kSpeechOutputLevel = 99;
+const CallStatistics kCallStats = {
+ 345, 678, 901, 234, -12, 3456, 7890, 567, 890, 123};
+const CodecInst kCodecInst = {
+ 123, "codec_name_recv", 96000, -187, 0, -103};
+const NetworkStatistics kNetworkStats = {
+ 123, 456, false, 0, 0, 789, 12, 345, 678, 901, -1, -1, -1, -1, -1, 0};
+const AudioDecodingCallStats kAudioDecodeStats = MakeAudioDecodeStatsForTest();
+
+struct ConfigHelper {
+ ConfigHelper()
+ : simulated_clock_(123456),
+ call_stats_(&simulated_clock_),
+ congestion_controller_(&process_thread_,
+ &call_stats_,
+ &bitrate_observer_) {
+ using testing::Invoke;
+
+ EXPECT_CALL(voice_engine_,
+ RegisterVoiceEngineObserver(_)).WillOnce(Return(0));
+ EXPECT_CALL(voice_engine_,
+ DeRegisterVoiceEngineObserver()).WillOnce(Return(0));
+ AudioState::Config config;
+ config.voice_engine = &voice_engine_;
+ audio_state_ = AudioState::Create(config);
+
+ EXPECT_CALL(voice_engine_, ChannelProxyFactory(kChannelId))
+ .WillOnce(Invoke([this](int channel_id) {
+ EXPECT_FALSE(channel_proxy_);
+ channel_proxy_ = new testing::StrictMock<MockVoEChannelProxy>();
+ EXPECT_CALL(*channel_proxy_, SetLocalSSRC(kLocalSsrc)).Times(1);
+ EXPECT_CALL(*channel_proxy_,
+ SetReceiveAbsoluteSenderTimeStatus(true, kAbsSendTimeId))
+ .Times(1);
+ EXPECT_CALL(*channel_proxy_,
+ SetReceiveAudioLevelIndicationStatus(true, kAudioLevelId))
+ .Times(1);
+ EXPECT_CALL(*channel_proxy_, SetCongestionControlObjects(
+ nullptr, nullptr, &packet_router_))
+ .Times(1);
+ EXPECT_CALL(congestion_controller_, packet_router())
+ .WillOnce(Return(&packet_router_));
+ EXPECT_CALL(*channel_proxy_,
+ SetCongestionControlObjects(nullptr, nullptr, nullptr))
+ .Times(1);
+ return channel_proxy_;
+ }));
+ stream_config_.voe_channel_id = kChannelId;
+ stream_config_.rtp.local_ssrc = kLocalSsrc;
+ stream_config_.rtp.remote_ssrc = kRemoteSsrc;
+ stream_config_.rtp.extensions.push_back(
+ RtpExtension(RtpExtension::kAbsSendTime, kAbsSendTimeId));
+ stream_config_.rtp.extensions.push_back(
+ RtpExtension(RtpExtension::kAudioLevel, kAudioLevelId));
+ }
+
+ MockCongestionController* congestion_controller() {
+ return &congestion_controller_;
+ }
+ MockRemoteBitrateEstimator* remote_bitrate_estimator() {
+ return &remote_bitrate_estimator_;
+ }
+ AudioReceiveStream::Config& config() { return stream_config_; }
+ rtc::scoped_refptr<AudioState> audio_state() { return audio_state_; }
+ MockVoiceEngine& voice_engine() { return voice_engine_; }
- const uint32_t kPosLength = 2;
- ByteWriter<uint16_t>::WriteBigEndian(buffer + kPosLength,
- kAbsoluteSendTimeLength / 4);
+ void SetupMockForBweFeedback(bool send_side_bwe) {
+ EXPECT_CALL(congestion_controller_,
+ GetRemoteBitrateEstimator(send_side_bwe))
+ .WillOnce(Return(&remote_bitrate_estimator_));
+ EXPECT_CALL(remote_bitrate_estimator_,
+ RemoveStream(stream_config_.rtp.remote_ssrc));
+ }
- const uint8_t kLengthOfData = 3;
- buffer[kRtpOneByteHeaderLength] = (id << 4) + (kLengthOfData - 1);
- ByteWriter<uint32_t, kLengthOfData>::WriteBigEndian(
- buffer + kRtpOneByteHeaderLength + 1, abs_send_time);
+ void SetupMockForGetStats() {
+ using testing::DoAll;
+ using testing::SetArgReferee;
+
+ ASSERT_TRUE(channel_proxy_);
+ EXPECT_CALL(*channel_proxy_, GetRTCPStatistics())
+ .WillOnce(Return(kCallStats));
+ EXPECT_CALL(*channel_proxy_, GetDelayEstimate())
+ .WillOnce(Return(kJitterBufferDelay + kPlayoutBufferDelay));
+ EXPECT_CALL(*channel_proxy_, GetSpeechOutputLevelFullRange())
+ .WillOnce(Return(kSpeechOutputLevel));
+ EXPECT_CALL(*channel_proxy_, GetNetworkStatistics())
+ .WillOnce(Return(kNetworkStats));
+ EXPECT_CALL(*channel_proxy_, GetDecodingCallStatistics())
+ .WillOnce(Return(kAudioDecodeStats));
+
+ EXPECT_CALL(voice_engine_, GetRecCodec(kChannelId, _))
+ .WillOnce(DoAll(SetArgReferee<1>(kCodecInst), Return(0)));
+ }
+
+ private:
+ SimulatedClock simulated_clock_;
+ CallStats call_stats_;
+ PacketRouter packet_router_;
+ testing::NiceMock<MockBitrateObserver> bitrate_observer_;
+ testing::NiceMock<MockProcessThread> process_thread_;
+ MockCongestionController congestion_controller_;
+ MockRemoteBitrateEstimator remote_bitrate_estimator_;
+ testing::StrictMock<MockVoiceEngine> voice_engine_;
+ rtc::scoped_refptr<AudioState> audio_state_;
+ AudioReceiveStream::Config stream_config_;
+ testing::StrictMock<MockVoEChannelProxy>* channel_proxy_ = nullptr;
+};
+
+void BuildOneByteExtension(std::vector<uint8_t>::iterator it,
+ int id,
+ uint32_t extension_value,
+ size_t value_length) {
+ const uint16_t kRtpOneByteHeaderExtensionId = 0xBEDE;
+ ByteWriter<uint16_t>::WriteBigEndian(&(*it), kRtpOneByteHeaderExtensionId);
+ it += 2;
+
+ ByteWriter<uint16_t>::WriteBigEndian(&(*it), kOneByteExtensionLength / 4);
+ it += 2;
+ const size_t kExtensionDataLength = kOneByteExtensionLength - 1;
+ uint32_t shifted_value = extension_value
+ << (8 * (kExtensionDataLength - value_length));
+ *it = (id << 4) + (value_length - 1);
+ ++it;
+ ByteWriter<uint32_t, kExtensionDataLength>::WriteBigEndian(&(*it),
+ shifted_value);
}
-size_t CreateRtpHeaderWithAbsSendTime(uint8_t* header,
- int extension_id,
- uint32_t abs_send_time) {
+std::vector<uint8_t> CreateRtpHeaderWithOneByteExtension(
+ int extension_id,
+ uint32_t extension_value,
+ size_t value_length) {
+ std::vector<uint8_t> header;
+ header.resize(webrtc::kRtpHeaderSize + kOneByteExtensionHeaderLength +
+ kOneByteExtensionLength);
header[0] = 0x80; // Version 2.
header[0] |= 0x10; // Set extension bit.
header[1] = 100; // Payload type.
header[1] |= 0x80; // Marker bit is set.
- ByteWriter<uint16_t>::WriteBigEndian(header + 2, 0x1234); // Sequence number.
- ByteWriter<uint32_t>::WriteBigEndian(header + 4, 0x5678); // Timestamp.
- ByteWriter<uint32_t>::WriteBigEndian(header + 8, 0x4321); // SSRC.
- int32_t rtp_header_length = webrtc::kRtpHeaderSize;
-
- BuildAbsoluteSendTimeExtension(header + rtp_header_length, extension_id,
- abs_send_time);
- rtp_header_length += kAbsoluteSendTimeLength;
- return rtp_header_length;
+ ByteWriter<uint16_t>::WriteBigEndian(&header[2], 0x1234); // Sequence number.
+ ByteWriter<uint32_t>::WriteBigEndian(&header[4], 0x5678); // Timestamp.
+ ByteWriter<uint32_t>::WriteBigEndian(&header[8], 0x4321); // SSRC.
+
+ BuildOneByteExtension(header.begin() + webrtc::kRtpHeaderSize, extension_id,
+ extension_value, value_length);
+ return header;
}
} // namespace
-namespace webrtc {
-namespace test {
-
TEST(AudioReceiveStreamTest, ConfigToString) {
- const int kAbsSendTimeId = 3;
AudioReceiveStream::Config config;
- config.rtp.remote_ssrc = 1234;
- config.rtp.local_ssrc = 5678;
+ config.rtp.remote_ssrc = kRemoteSsrc;
+ config.rtp.local_ssrc = kLocalSsrc;
config.rtp.extensions.push_back(
RtpExtension(RtpExtension::kAbsSendTime, kAbsSendTimeId));
- config.voe_channel_id = 1;
+ config.voe_channel_id = kChannelId;
config.combined_audio_video_bwe = true;
- EXPECT_EQ("{rtp: {remote_ssrc: 1234, local_ssrc: 5678, extensions: [{name: "
- "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time, id: 3}]}, "
+ EXPECT_EQ(
+ "{rtp: {remote_ssrc: 1234, local_ssrc: 5678, extensions: [{name: "
+ "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time, id: 2}]}, "
"receive_transport: nullptr, rtcp_send_transport: nullptr, "
- "voe_channel_id: 1, combined_audio_video_bwe: true}", config.ToString());
+ "voe_channel_id: 2, combined_audio_video_bwe: true}",
+ config.ToString());
}
TEST(AudioReceiveStreamTest, ConstructDestruct) {
- MockRemoteBitrateEstimator remote_bitrate_estimator;
- FakeVoiceEngine voice_engine;
- AudioReceiveStream::Config config;
- config.voe_channel_id = 1;
- internal::AudioReceiveStream recv_stream(&remote_bitrate_estimator, config,
- &voice_engine);
+ ConfigHelper helper;
+ internal::AudioReceiveStream recv_stream(
+ helper.congestion_controller(), helper.config(), helper.audio_state());
+}
+
+MATCHER_P(VerifyHeaderExtension, expected_extension, "") {
+ return arg.extension.hasAbsoluteSendTime ==
+ expected_extension.hasAbsoluteSendTime &&
+ arg.extension.absoluteSendTime ==
+ expected_extension.absoluteSendTime &&
+ arg.extension.hasTransportSequenceNumber ==
+ expected_extension.hasTransportSequenceNumber &&
+ arg.extension.transportSequenceNumber ==
+ expected_extension.transportSequenceNumber;
}
TEST(AudioReceiveStreamTest, AudioPacketUpdatesBweWithTimestamp) {
- MockRemoteBitrateEstimator remote_bitrate_estimator;
- FakeVoiceEngine voice_engine;
- AudioReceiveStream::Config config;
- config.combined_audio_video_bwe = true;
- config.voe_channel_id = FakeVoiceEngine::kRecvChannelId;
- const int kAbsSendTimeId = 3;
- config.rtp.extensions.push_back(
- RtpExtension(RtpExtension::kAbsSendTime, kAbsSendTimeId));
- internal::AudioReceiveStream recv_stream(&remote_bitrate_estimator, config,
- &voice_engine);
- uint8_t rtp_packet[30];
+ ConfigHelper helper;
+ helper.config().combined_audio_video_bwe = true;
+ helper.SetupMockForBweFeedback(false);
+ internal::AudioReceiveStream recv_stream(
+ helper.congestion_controller(), helper.config(), helper.audio_state());
const int kAbsSendTimeValue = 1234;
- CreateRtpHeaderWithAbsSendTime(rtp_packet, kAbsSendTimeId, kAbsSendTimeValue);
+ std::vector<uint8_t> rtp_packet =
+ CreateRtpHeaderWithOneByteExtension(kAbsSendTimeId, kAbsSendTimeValue, 3);
PacketTime packet_time(5678000, 0);
const size_t kExpectedHeaderLength = 20;
- EXPECT_CALL(remote_bitrate_estimator,
- IncomingPacket(packet_time.timestamp / 1000,
- sizeof(rtp_packet) - kExpectedHeaderLength, testing::_, false))
+ RTPHeaderExtension expected_extension;
+ expected_extension.hasAbsoluteSendTime = true;
+ expected_extension.absoluteSendTime = kAbsSendTimeValue;
+ EXPECT_CALL(*helper.remote_bitrate_estimator(),
+ IncomingPacket(packet_time.timestamp / 1000,
+ rtp_packet.size() - kExpectedHeaderLength,
+ VerifyHeaderExtension(expected_extension), false))
.Times(1);
EXPECT_TRUE(
- recv_stream.DeliverRtp(rtp_packet, sizeof(rtp_packet), packet_time));
+ recv_stream.DeliverRtp(&rtp_packet[0], rtp_packet.size(), packet_time));
}
-TEST(AudioReceiveStreamTest, GetStats) {
- MockRemoteBitrateEstimator remote_bitrate_estimator;
- FakeVoiceEngine voice_engine;
- AudioReceiveStream::Config config;
- config.rtp.remote_ssrc = FakeVoiceEngine::kRecvSsrc;
- config.voe_channel_id = FakeVoiceEngine::kRecvChannelId;
- internal::AudioReceiveStream recv_stream(&remote_bitrate_estimator, config,
- &voice_engine);
+TEST(AudioReceiveStreamTest, AudioPacketUpdatesBweFeedback) {
+ ConfigHelper helper;
+ helper.config().combined_audio_video_bwe = true;
+ helper.config().rtp.transport_cc = true;
+ helper.config().rtp.extensions.push_back(RtpExtension(
+ RtpExtension::kTransportSequenceNumber, kTransportSequenceNumberId));
+ helper.SetupMockForBweFeedback(true);
+ internal::AudioReceiveStream recv_stream(
+ helper.congestion_controller(), helper.config(), helper.audio_state());
+ const int kTransportSequenceNumberValue = 1234;
+ std::vector<uint8_t> rtp_packet = CreateRtpHeaderWithOneByteExtension(
+ kTransportSequenceNumberId, kTransportSequenceNumberValue, 2);
+ PacketTime packet_time(5678000, 0);
+ const size_t kExpectedHeaderLength = 20;
+ RTPHeaderExtension expected_extension;
+ expected_extension.hasTransportSequenceNumber = true;
+ expected_extension.transportSequenceNumber = kTransportSequenceNumberValue;
+ EXPECT_CALL(*helper.remote_bitrate_estimator(),
+ IncomingPacket(packet_time.timestamp / 1000,
+ rtp_packet.size() - kExpectedHeaderLength,
+ VerifyHeaderExtension(expected_extension), false))
+ .Times(1);
+ EXPECT_TRUE(
+ recv_stream.DeliverRtp(&rtp_packet[0], rtp_packet.size(), packet_time));
+}
+TEST(AudioReceiveStreamTest, GetStats) {
+ ConfigHelper helper;
+ internal::AudioReceiveStream recv_stream(
+ helper.congestion_controller(), helper.config(), helper.audio_state());
+ helper.SetupMockForGetStats();
AudioReceiveStream::Stats stats = recv_stream.GetStats();
- const CallStatistics& call_stats = FakeVoiceEngine::kRecvCallStats;
- const CodecInst& codec_inst = FakeVoiceEngine::kRecvCodecInst;
- const NetworkStatistics& net_stats = FakeVoiceEngine::kRecvNetworkStats;
- const AudioDecodingCallStats& decode_stats =
- FakeVoiceEngine::kRecvAudioDecodingCallStats;
- EXPECT_EQ(FakeVoiceEngine::kRecvSsrc, stats.remote_ssrc);
- EXPECT_EQ(static_cast<int64_t>(call_stats.bytesReceived), stats.bytes_rcvd);
- EXPECT_EQ(static_cast<uint32_t>(call_stats.packetsReceived),
+ EXPECT_EQ(kRemoteSsrc, stats.remote_ssrc);
+ EXPECT_EQ(static_cast<int64_t>(kCallStats.bytesReceived), stats.bytes_rcvd);
+ EXPECT_EQ(static_cast<uint32_t>(kCallStats.packetsReceived),
stats.packets_rcvd);
- EXPECT_EQ(call_stats.cumulativeLost, stats.packets_lost);
- EXPECT_EQ(Q8ToFloat(call_stats.fractionLost), stats.fraction_lost);
- EXPECT_EQ(std::string(codec_inst.plname), stats.codec_name);
- EXPECT_EQ(call_stats.extendedMax, stats.ext_seqnum);
- EXPECT_EQ(call_stats.jitterSamples / (codec_inst.plfreq / 1000),
+ EXPECT_EQ(kCallStats.cumulativeLost, stats.packets_lost);
+ EXPECT_EQ(Q8ToFloat(kCallStats.fractionLost), stats.fraction_lost);
+ EXPECT_EQ(std::string(kCodecInst.plname), stats.codec_name);
+ EXPECT_EQ(kCallStats.extendedMax, stats.ext_seqnum);
+ EXPECT_EQ(kCallStats.jitterSamples / (kCodecInst.plfreq / 1000),
stats.jitter_ms);
- EXPECT_EQ(net_stats.currentBufferSize, stats.jitter_buffer_ms);
- EXPECT_EQ(net_stats.preferredBufferSize, stats.jitter_buffer_preferred_ms);
- EXPECT_EQ(static_cast<uint32_t>(FakeVoiceEngine::kRecvJitterBufferDelay +
- FakeVoiceEngine::kRecvPlayoutBufferDelay), stats.delay_estimate_ms);
- EXPECT_EQ(static_cast<int32_t>(FakeVoiceEngine::kRecvSpeechOutputLevel),
- stats.audio_level);
- EXPECT_EQ(Q14ToFloat(net_stats.currentExpandRate), stats.expand_rate);
- EXPECT_EQ(Q14ToFloat(net_stats.currentSpeechExpandRate),
+ EXPECT_EQ(kNetworkStats.currentBufferSize, stats.jitter_buffer_ms);
+ EXPECT_EQ(kNetworkStats.preferredBufferSize,
+ stats.jitter_buffer_preferred_ms);
+ EXPECT_EQ(static_cast<uint32_t>(kJitterBufferDelay + kPlayoutBufferDelay),
+ stats.delay_estimate_ms);
+ EXPECT_EQ(static_cast<int32_t>(kSpeechOutputLevel), stats.audio_level);
+ EXPECT_EQ(Q14ToFloat(kNetworkStats.currentExpandRate), stats.expand_rate);
+ EXPECT_EQ(Q14ToFloat(kNetworkStats.currentSpeechExpandRate),
stats.speech_expand_rate);
- EXPECT_EQ(Q14ToFloat(net_stats.currentSecondaryDecodedRate),
+ EXPECT_EQ(Q14ToFloat(kNetworkStats.currentSecondaryDecodedRate),
stats.secondary_decoded_rate);
- EXPECT_EQ(Q14ToFloat(net_stats.currentAccelerateRate), stats.accelerate_rate);
- EXPECT_EQ(Q14ToFloat(net_stats.currentPreemptiveRate),
+ EXPECT_EQ(Q14ToFloat(kNetworkStats.currentAccelerateRate),
+ stats.accelerate_rate);
+ EXPECT_EQ(Q14ToFloat(kNetworkStats.currentPreemptiveRate),
stats.preemptive_expand_rate);
- EXPECT_EQ(decode_stats.calls_to_silence_generator,
+ EXPECT_EQ(kAudioDecodeStats.calls_to_silence_generator,
stats.decoding_calls_to_silence_generator);
- EXPECT_EQ(decode_stats.calls_to_neteq, stats.decoding_calls_to_neteq);
- EXPECT_EQ(decode_stats.decoded_normal, stats.decoding_normal);
- EXPECT_EQ(decode_stats.decoded_plc, stats.decoding_plc);
- EXPECT_EQ(decode_stats.decoded_cng, stats.decoding_cng);
- EXPECT_EQ(decode_stats.decoded_plc_cng, stats.decoding_plc_cng);
- EXPECT_EQ(call_stats.capture_start_ntp_time_ms_,
+ EXPECT_EQ(kAudioDecodeStats.calls_to_neteq, stats.decoding_calls_to_neteq);
+ EXPECT_EQ(kAudioDecodeStats.decoded_normal, stats.decoding_normal);
+ EXPECT_EQ(kAudioDecodeStats.decoded_plc, stats.decoding_plc);
+ EXPECT_EQ(kAudioDecodeStats.decoded_cng, stats.decoding_cng);
+ EXPECT_EQ(kAudioDecodeStats.decoded_plc_cng, stats.decoding_plc_cng);
+ EXPECT_EQ(kCallStats.capture_start_ntp_time_ms_,
stats.capture_start_ntp_time_ms);
}
} // namespace test
diff --git a/webrtc/audio/audio_send_stream.cc b/webrtc/audio/audio_send_stream.cc
index ccfdca546d..35a65521dd 100644
--- a/webrtc/audio/audio_send_stream.cc
+++ b/webrtc/audio/audio_send_stream.cc
@@ -12,13 +12,20 @@
#include <string>
+#include "webrtc/audio/audio_state.h"
#include "webrtc/audio/conversion.h"
+#include "webrtc/audio/scoped_voe_interface.h"
#include "webrtc/base/checks.h"
#include "webrtc/base/logging.h"
+#include "webrtc/call/congestion_controller.h"
+#include "webrtc/modules/pacing/paced_sender.h"
+#include "webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h"
+#include "webrtc/voice_engine/channel_proxy.h"
#include "webrtc/voice_engine/include/voe_audio_processing.h"
#include "webrtc/voice_engine/include/voe_codec.h"
#include "webrtc/voice_engine/include/voe_rtp_rtcp.h"
#include "webrtc/voice_engine/include/voe_volume_control.h"
+#include "webrtc/voice_engine/voice_engine_impl.h"
namespace webrtc {
std::string AudioSendStream::Config::Rtp::ToString() const {
@@ -32,6 +39,7 @@ std::string AudioSendStream::Config::Rtp::ToString() const {
}
}
ss << ']';
+ ss << ", c_name: " << c_name;
ss << '}';
return ss.str();
}
@@ -48,38 +56,91 @@ std::string AudioSendStream::Config::ToString() const {
}
namespace internal {
-AudioSendStream::AudioSendStream(const webrtc::AudioSendStream::Config& config,
- VoiceEngine* voice_engine)
- : config_(config),
- voice_engine_(voice_engine),
- voe_base_(voice_engine) {
+AudioSendStream::AudioSendStream(
+ const webrtc::AudioSendStream::Config& config,
+ const rtc::scoped_refptr<webrtc::AudioState>& audio_state,
+ CongestionController* congestion_controller)
+ : config_(config), audio_state_(audio_state) {
LOG(LS_INFO) << "AudioSendStream: " << config_.ToString();
- RTC_DCHECK_NE(config.voe_channel_id, -1);
- RTC_DCHECK(voice_engine_);
+ RTC_DCHECK_NE(config_.voe_channel_id, -1);
+ RTC_DCHECK(audio_state_.get());
+ RTC_DCHECK(congestion_controller);
+
+ VoiceEngineImpl* voe_impl = static_cast<VoiceEngineImpl*>(voice_engine());
+ channel_proxy_ = voe_impl->GetChannelProxy(config_.voe_channel_id);
+ channel_proxy_->SetCongestionControlObjects(
+ congestion_controller->pacer(),
+ congestion_controller->GetTransportFeedbackObserver(),
+ congestion_controller->packet_router());
+ channel_proxy_->SetRTCPStatus(true);
+ channel_proxy_->SetLocalSSRC(config.rtp.ssrc);
+ channel_proxy_->SetRTCP_CNAME(config.rtp.c_name);
+
+ for (const auto& extension : config.rtp.extensions) {
+ if (extension.name == RtpExtension::kAbsSendTime) {
+ channel_proxy_->SetSendAbsoluteSenderTimeStatus(true, extension.id);
+ } else if (extension.name == RtpExtension::kAudioLevel) {
+ channel_proxy_->SetSendAudioLevelIndicationStatus(true, extension.id);
+ } else if (extension.name == RtpExtension::kTransportSequenceNumber) {
+ channel_proxy_->EnableSendTransportSequenceNumber(extension.id);
+ } else {
+ RTC_NOTREACHED() << "Registering unsupported RTP extension.";
+ }
+ }
}
AudioSendStream::~AudioSendStream() {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
LOG(LS_INFO) << "~AudioSendStream: " << config_.ToString();
+ channel_proxy_->SetCongestionControlObjects(nullptr, nullptr, nullptr);
+}
+
+void AudioSendStream::Start() {
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void AudioSendStream::Stop() {
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void AudioSendStream::SignalNetworkState(NetworkState state) {
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+bool AudioSendStream::DeliverRtcp(const uint8_t* packet, size_t length) {
+ // TODO(solenberg): Tests call this function on a network thread, libjingle
+ // calls on the worker thread. We should move towards always using a network
+ // thread. Then this check can be enabled.
+ // RTC_DCHECK(!thread_checker_.CalledOnValidThread());
+ return false;
+}
+
+bool AudioSendStream::SendTelephoneEvent(int payload_type, uint8_t event,
+ uint32_t duration_ms) {
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+ return channel_proxy_->SetSendTelephoneEventPayloadType(payload_type) &&
+ channel_proxy_->SendTelephoneEventOutband(event, duration_ms);
}
webrtc::AudioSendStream::Stats AudioSendStream::GetStats() const {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
webrtc::AudioSendStream::Stats stats;
stats.local_ssrc = config_.rtp.ssrc;
- ScopedVoEInterface<VoEAudioProcessing> processing(voice_engine_);
- ScopedVoEInterface<VoECodec> codec(voice_engine_);
- ScopedVoEInterface<VoERTP_RTCP> rtp(voice_engine_);
- ScopedVoEInterface<VoEVolumeControl> volume(voice_engine_);
- unsigned int ssrc = 0;
- webrtc::CallStatistics call_stats = {0};
- if (rtp->GetLocalSSRC(config_.voe_channel_id, ssrc) == -1 ||
- rtp->GetRTCPStatistics(config_.voe_channel_id, call_stats) == -1) {
- return stats;
- }
+ ScopedVoEInterface<VoEAudioProcessing> processing(voice_engine());
+ ScopedVoEInterface<VoECodec> codec(voice_engine());
+ ScopedVoEInterface<VoEVolumeControl> volume(voice_engine());
+ webrtc::CallStatistics call_stats = channel_proxy_->GetRTCPStatistics();
stats.bytes_sent = call_stats.bytesSent;
stats.packets_sent = call_stats.packetsSent;
+ // RTT isn't known until a RTCP report is received. Until then, VoiceEngine
+ // returns 0 to indicate an error value.
+ if (call_stats.rttMs > 0) {
+ stats.rtt_ms = call_stats.rttMs;
+ }
+ // TODO(solenberg): [was ajm]: Re-enable this metric once we have a reliable
+ // implementation.
+ stats.aec_quality_min = -1;
webrtc::CodecInst codec_inst = {0};
if (codec->GetSendCodec(config_.voe_channel_id, codec_inst) != -1) {
@@ -87,54 +148,43 @@ webrtc::AudioSendStream::Stats AudioSendStream::GetStats() const {
stats.codec_name = codec_inst.plname;
// Get data from the last remote RTCP report.
- std::vector<webrtc::ReportBlock> blocks;
- if (rtp->GetRemoteRTCPReportBlocks(config_.voe_channel_id, &blocks) != -1) {
- for (const webrtc::ReportBlock& block : blocks) {
- // Lookup report for send ssrc only.
- if (block.source_SSRC == stats.local_ssrc) {
- stats.packets_lost = block.cumulative_num_packets_lost;
- stats.fraction_lost = Q8ToFloat(block.fraction_lost);
- stats.ext_seqnum = block.extended_highest_sequence_number;
- // Convert samples to milliseconds.
- if (codec_inst.plfreq / 1000 > 0) {
- stats.jitter_ms =
- block.interarrival_jitter / (codec_inst.plfreq / 1000);
- }
- break;
+ for (const auto& block : channel_proxy_->GetRemoteRTCPReportBlocks()) {
+ // Lookup report for send ssrc only.
+ if (block.source_SSRC == stats.local_ssrc) {
+ stats.packets_lost = block.cumulative_num_packets_lost;
+ stats.fraction_lost = Q8ToFloat(block.fraction_lost);
+ stats.ext_seqnum = block.extended_highest_sequence_number;
+ // Convert samples to milliseconds.
+ if (codec_inst.plfreq / 1000 > 0) {
+ stats.jitter_ms =
+ block.interarrival_jitter / (codec_inst.plfreq / 1000);
}
+ break;
}
}
}
- // RTT isn't known until a RTCP report is received. Until then, VoiceEngine
- // returns 0 to indicate an error value.
- if (call_stats.rttMs > 0) {
- stats.rtt_ms = call_stats.rttMs;
- }
-
// Local speech level.
{
unsigned int level = 0;
- if (volume->GetSpeechInputLevelFullRange(level) != -1) {
- stats.audio_level = static_cast<int32_t>(level);
- }
+ int error = volume->GetSpeechInputLevelFullRange(level);
+ RTC_DCHECK_EQ(0, error);
+ stats.audio_level = static_cast<int32_t>(level);
}
- // TODO(ajm): Re-enable this metric once we have a reliable implementation.
- stats.aec_quality_min = -1;
-
bool echo_metrics_on = false;
- if (processing->GetEcMetricsStatus(echo_metrics_on) != -1 &&
- echo_metrics_on) {
+ int error = processing->GetEcMetricsStatus(echo_metrics_on);
+ RTC_DCHECK_EQ(0, error);
+ if (echo_metrics_on) {
// These can also be negative, but in practice -1 is only used to signal
// insufficient data, since the resolution is limited to multiples of 4 ms.
int median = -1;
int std = -1;
float dummy = 0.0f;
- if (processing->GetEcDelayMetrics(median, std, dummy) != -1) {
- stats.echo_delay_median_ms = median;
- stats.echo_delay_std_ms = std;
- }
+ error = processing->GetEcDelayMetrics(median, std, dummy);
+ RTC_DCHECK_EQ(0, error);
+ stats.echo_delay_median_ms = median;
+ stats.echo_delay_std_ms = std;
// These can take on valid negative values, so use the lowest possible level
// as default rather than -1.
@@ -142,14 +192,15 @@ webrtc::AudioSendStream::Stats AudioSendStream::GetStats() const {
int erle = -100;
int dummy1 = 0;
int dummy2 = 0;
- if (processing->GetEchoMetrics(erl, erle, dummy1, dummy2) != -1) {
- stats.echo_return_loss = erl;
- stats.echo_return_loss_enhancement = erle;
- }
+ error = processing->GetEchoMetrics(erl, erle, dummy1, dummy2);
+ RTC_DCHECK_EQ(0, error);
+ stats.echo_return_loss = erl;
+ stats.echo_return_loss_enhancement = erle;
}
- // TODO(solenberg): Collect typing noise warnings here too!
- // bool typing_noise_detected = typing_noise_detected_;
+ internal::AudioState* audio_state =
+ static_cast<internal::AudioState*>(audio_state_.get());
+ stats.typing_noise_detected = audio_state->typing_noise_detected();
return stats;
}
@@ -159,24 +210,12 @@ const webrtc::AudioSendStream::Config& AudioSendStream::config() const {
return config_;
}
-void AudioSendStream::Start() {
- RTC_DCHECK(thread_checker_.CalledOnValidThread());
-}
-
-void AudioSendStream::Stop() {
- RTC_DCHECK(thread_checker_.CalledOnValidThread());
-}
-
-void AudioSendStream::SignalNetworkState(NetworkState state) {
- RTC_DCHECK(thread_checker_.CalledOnValidThread());
-}
-
-bool AudioSendStream::DeliverRtcp(const uint8_t* packet, size_t length) {
- // TODO(solenberg): Tests call this function on a network thread, libjingle
- // calls on the worker thread. We should move towards always using a network
- // thread. Then this check can be enabled.
- // RTC_DCHECK(!thread_checker_.CalledOnValidThread());
- return false;
+VoiceEngine* AudioSendStream::voice_engine() const {
+ internal::AudioState* audio_state =
+ static_cast<internal::AudioState*>(audio_state_.get());
+ VoiceEngine* voice_engine = audio_state->voice_engine();
+ RTC_DCHECK(voice_engine);
+ return voice_engine;
}
} // namespace internal
} // namespace webrtc
diff --git a/webrtc/audio/audio_send_stream.h b/webrtc/audio/audio_send_stream.h
index ae81dfc8fc..8b96350590 100644
--- a/webrtc/audio/audio_send_stream.h
+++ b/webrtc/audio/audio_send_stream.h
@@ -12,20 +12,24 @@
#define WEBRTC_AUDIO_AUDIO_SEND_STREAM_H_
#include "webrtc/audio_send_stream.h"
-#include "webrtc/audio/scoped_voe_interface.h"
+#include "webrtc/audio_state.h"
#include "webrtc/base/thread_checker.h"
-#include "webrtc/voice_engine/include/voe_base.h"
+#include "webrtc/base/scoped_ptr.h"
namespace webrtc {
-
+class CongestionController;
class VoiceEngine;
-namespace internal {
+namespace voe {
+class ChannelProxy;
+} // namespace voe
+namespace internal {
class AudioSendStream final : public webrtc::AudioSendStream {
public:
AudioSendStream(const webrtc::AudioSendStream::Config& config,
- VoiceEngine* voice_engine);
+ const rtc::scoped_refptr<webrtc::AudioState>& audio_state,
+ CongestionController* congestion_controller);
~AudioSendStream() override;
// webrtc::SendStream implementation.
@@ -35,16 +39,19 @@ class AudioSendStream final : public webrtc::AudioSendStream {
bool DeliverRtcp(const uint8_t* packet, size_t length) override;
// webrtc::AudioSendStream implementation.
+ bool SendTelephoneEvent(int payload_type, uint8_t event,
+ uint32_t duration_ms) override;
webrtc::AudioSendStream::Stats GetStats() const override;
const webrtc::AudioSendStream::Config& config() const;
private:
+ VoiceEngine* voice_engine() const;
+
rtc::ThreadChecker thread_checker_;
const webrtc::AudioSendStream::Config config_;
- VoiceEngine* voice_engine_;
- // We hold one interface pointer to the VoE to make sure it is kept alive.
- ScopedVoEInterface<VoEBase> voe_base_;
+ rtc::scoped_refptr<webrtc::AudioState> audio_state_;
+ rtc::scoped_ptr<voe::ChannelProxy> channel_proxy_;
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(AudioSendStream);
};
diff --git a/webrtc/audio/audio_send_stream_unittest.cc b/webrtc/audio/audio_send_stream_unittest.cc
index 227ec83799..466c1571ac 100644
--- a/webrtc/audio/audio_send_stream_unittest.cc
+++ b/webrtc/audio/audio_send_stream_unittest.cc
@@ -8,69 +8,238 @@
* be found in the AUTHORS file in the root of the source tree.
*/
+#include <string>
+#include <vector>
+
#include "testing/gtest/include/gtest/gtest.h"
#include "webrtc/audio/audio_send_stream.h"
+#include "webrtc/audio/audio_state.h"
#include "webrtc/audio/conversion.h"
-#include "webrtc/test/fake_voice_engine.h"
+#include "webrtc/call/congestion_controller.h"
+#include "webrtc/modules/bitrate_controller/include/mock/mock_bitrate_controller.h"
+#include "webrtc/modules/pacing/paced_sender.h"
+#include "webrtc/test/mock_voe_channel_proxy.h"
+#include "webrtc/test/mock_voice_engine.h"
+#include "webrtc/video/call_stats.h"
namespace webrtc {
namespace test {
+namespace {
+
+using testing::_;
+using testing::Return;
+
+const int kChannelId = 1;
+const uint32_t kSsrc = 1234;
+const char* kCName = "foo_name";
+const int kAudioLevelId = 2;
+const int kAbsSendTimeId = 3;
+const int kTransportSequenceNumberId = 4;
+const int kEchoDelayMedian = 254;
+const int kEchoDelayStdDev = -3;
+const int kEchoReturnLoss = -65;
+const int kEchoReturnLossEnhancement = 101;
+const unsigned int kSpeechInputLevel = 96;
+const CallStatistics kCallStats = {
+ 1345, 1678, 1901, 1234, 112, 13456, 17890, 1567, -1890, -1123};
+const CodecInst kCodecInst = {-121, "codec_name_send", 48000, -231, 0, -671};
+const ReportBlock kReportBlock = {456, 780, 123, 567, 890, 132, 143, 13354};
+const int kTelephoneEventPayloadType = 123;
+const uint8_t kTelephoneEventCode = 45;
+const uint32_t kTelephoneEventDuration = 6789;
+
+struct ConfigHelper {
+ ConfigHelper()
+ : stream_config_(nullptr),
+ call_stats_(Clock::GetRealTimeClock()),
+ process_thread_(ProcessThread::Create("AudioTestThread")),
+ congestion_controller_(process_thread_.get(),
+ &call_stats_,
+ &bitrate_observer_) {
+ using testing::Invoke;
+ using testing::StrEq;
+
+ EXPECT_CALL(voice_engine_,
+ RegisterVoiceEngineObserver(_)).WillOnce(Return(0));
+ EXPECT_CALL(voice_engine_,
+ DeRegisterVoiceEngineObserver()).WillOnce(Return(0));
+ AudioState::Config config;
+ config.voice_engine = &voice_engine_;
+ audio_state_ = AudioState::Create(config);
+
+ EXPECT_CALL(voice_engine_, ChannelProxyFactory(kChannelId))
+ .WillOnce(Invoke([this](int channel_id) {
+ EXPECT_FALSE(channel_proxy_);
+ channel_proxy_ = new testing::StrictMock<MockVoEChannelProxy>();
+ EXPECT_CALL(*channel_proxy_, SetRTCPStatus(true)).Times(1);
+ EXPECT_CALL(*channel_proxy_, SetLocalSSRC(kSsrc)).Times(1);
+ EXPECT_CALL(*channel_proxy_, SetRTCP_CNAME(StrEq(kCName))).Times(1);
+ EXPECT_CALL(*channel_proxy_,
+ SetSendAbsoluteSenderTimeStatus(true, kAbsSendTimeId)).Times(1);
+ EXPECT_CALL(*channel_proxy_,
+ SetSendAudioLevelIndicationStatus(true, kAudioLevelId)).Times(1);
+ EXPECT_CALL(*channel_proxy_, EnableSendTransportSequenceNumber(
+ kTransportSequenceNumberId))
+ .Times(1);
+ EXPECT_CALL(*channel_proxy_,
+ SetCongestionControlObjects(
+ congestion_controller_.pacer(),
+ congestion_controller_.GetTransportFeedbackObserver(),
+ congestion_controller_.packet_router()))
+ .Times(1);
+ EXPECT_CALL(*channel_proxy_,
+ SetCongestionControlObjects(nullptr, nullptr, nullptr))
+ .Times(1);
+ return channel_proxy_;
+ }));
+ stream_config_.voe_channel_id = kChannelId;
+ stream_config_.rtp.ssrc = kSsrc;
+ stream_config_.rtp.c_name = kCName;
+ stream_config_.rtp.extensions.push_back(
+ RtpExtension(RtpExtension::kAudioLevel, kAudioLevelId));
+ stream_config_.rtp.extensions.push_back(
+ RtpExtension(RtpExtension::kAbsSendTime, kAbsSendTimeId));
+ stream_config_.rtp.extensions.push_back(RtpExtension(
+ RtpExtension::kTransportSequenceNumber, kTransportSequenceNumberId));
+ }
+
+ AudioSendStream::Config& config() { return stream_config_; }
+ rtc::scoped_refptr<AudioState> audio_state() { return audio_state_; }
+ CongestionController* congestion_controller() {
+ return &congestion_controller_;
+ }
+
+ void SetupMockForSendTelephoneEvent() {
+ EXPECT_TRUE(channel_proxy_);
+ EXPECT_CALL(*channel_proxy_,
+ SetSendTelephoneEventPayloadType(kTelephoneEventPayloadType))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*channel_proxy_,
+ SendTelephoneEventOutband(kTelephoneEventCode, kTelephoneEventDuration))
+ .WillOnce(Return(true));
+ }
+
+ void SetupMockForGetStats() {
+ using testing::DoAll;
+ using testing::SetArgReferee;
+
+ std::vector<ReportBlock> report_blocks;
+ webrtc::ReportBlock block = kReportBlock;
+ report_blocks.push_back(block); // Has wrong SSRC.
+ block.source_SSRC = kSsrc;
+ report_blocks.push_back(block); // Correct block.
+ block.fraction_lost = 0;
+ report_blocks.push_back(block); // Duplicate SSRC, bad fraction_lost.
+
+ EXPECT_TRUE(channel_proxy_);
+ EXPECT_CALL(*channel_proxy_, GetRTCPStatistics())
+ .WillRepeatedly(Return(kCallStats));
+ EXPECT_CALL(*channel_proxy_, GetRemoteRTCPReportBlocks())
+ .WillRepeatedly(Return(report_blocks));
+
+ EXPECT_CALL(voice_engine_, GetSendCodec(kChannelId, _))
+ .WillRepeatedly(DoAll(SetArgReferee<1>(kCodecInst), Return(0)));
+ EXPECT_CALL(voice_engine_, GetSpeechInputLevelFullRange(_))
+ .WillRepeatedly(DoAll(SetArgReferee<0>(kSpeechInputLevel), Return(0)));
+ EXPECT_CALL(voice_engine_, GetEcMetricsStatus(_))
+ .WillRepeatedly(DoAll(SetArgReferee<0>(true), Return(0)));
+ EXPECT_CALL(voice_engine_, GetEchoMetrics(_, _, _, _))
+ .WillRepeatedly(DoAll(SetArgReferee<0>(kEchoReturnLoss),
+ SetArgReferee<1>(kEchoReturnLossEnhancement),
+ Return(0)));
+ EXPECT_CALL(voice_engine_, GetEcDelayMetrics(_, _, _))
+ .WillRepeatedly(DoAll(SetArgReferee<0>(kEchoDelayMedian),
+ SetArgReferee<1>(kEchoDelayStdDev), Return(0)));
+ }
+
+ private:
+ testing::StrictMock<MockVoiceEngine> voice_engine_;
+ rtc::scoped_refptr<AudioState> audio_state_;
+ AudioSendStream::Config stream_config_;
+ testing::StrictMock<MockVoEChannelProxy>* channel_proxy_ = nullptr;
+ CallStats call_stats_;
+ testing::NiceMock<MockBitrateObserver> bitrate_observer_;
+ rtc::scoped_ptr<ProcessThread> process_thread_;
+ CongestionController congestion_controller_;
+};
+} // namespace
TEST(AudioSendStreamTest, ConfigToString) {
- const int kAbsSendTimeId = 3;
AudioSendStream::Config config(nullptr);
- config.rtp.ssrc = 1234;
+ config.rtp.ssrc = kSsrc;
config.rtp.extensions.push_back(
RtpExtension(RtpExtension::kAbsSendTime, kAbsSendTimeId));
- config.voe_channel_id = 1;
+ config.rtp.c_name = kCName;
+ config.voe_channel_id = kChannelId;
config.cng_payload_type = 42;
config.red_payload_type = 17;
- EXPECT_EQ("{rtp: {ssrc: 1234, extensions: [{name: "
- "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time, id: 3}]}, "
- "voe_channel_id: 1, cng_payload_type: 42, red_payload_type: 17}",
+ EXPECT_EQ(
+ "{rtp: {ssrc: 1234, extensions: [{name: "
+ "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time, id: 3}], "
+ "c_name: foo_name}, voe_channel_id: 1, cng_payload_type: 42, "
+ "red_payload_type: 17}",
config.ToString());
}
TEST(AudioSendStreamTest, ConstructDestruct) {
- FakeVoiceEngine voice_engine;
- AudioSendStream::Config config(nullptr);
- config.voe_channel_id = 1;
- internal::AudioSendStream send_stream(config, &voice_engine);
+ ConfigHelper helper;
+ internal::AudioSendStream send_stream(helper.config(), helper.audio_state(),
+ helper.congestion_controller());
}
-TEST(AudioSendStreamTest, GetStats) {
- FakeVoiceEngine voice_engine;
- AudioSendStream::Config config(nullptr);
- config.rtp.ssrc = FakeVoiceEngine::kSendSsrc;
- config.voe_channel_id = FakeVoiceEngine::kSendChannelId;
- internal::AudioSendStream send_stream(config, &voice_engine);
+TEST(AudioSendStreamTest, SendTelephoneEvent) {
+ ConfigHelper helper;
+ internal::AudioSendStream send_stream(helper.config(), helper.audio_state(),
+ helper.congestion_controller());
+ helper.SetupMockForSendTelephoneEvent();
+ EXPECT_TRUE(send_stream.SendTelephoneEvent(kTelephoneEventPayloadType,
+ kTelephoneEventCode, kTelephoneEventDuration));
+}
+TEST(AudioSendStreamTest, GetStats) {
+ ConfigHelper helper;
+ internal::AudioSendStream send_stream(helper.config(), helper.audio_state(),
+ helper.congestion_controller());
+ helper.SetupMockForGetStats();
AudioSendStream::Stats stats = send_stream.GetStats();
- const CallStatistics& call_stats = FakeVoiceEngine::kSendCallStats;
- const CodecInst& codec_inst = FakeVoiceEngine::kSendCodecInst;
- const ReportBlock& report_block = FakeVoiceEngine::kSendReportBlock;
- EXPECT_EQ(FakeVoiceEngine::kSendSsrc, stats.local_ssrc);
- EXPECT_EQ(static_cast<int64_t>(call_stats.bytesSent), stats.bytes_sent);
- EXPECT_EQ(call_stats.packetsSent, stats.packets_sent);
- EXPECT_EQ(static_cast<int32_t>(report_block.cumulative_num_packets_lost),
+ EXPECT_EQ(kSsrc, stats.local_ssrc);
+ EXPECT_EQ(static_cast<int64_t>(kCallStats.bytesSent), stats.bytes_sent);
+ EXPECT_EQ(kCallStats.packetsSent, stats.packets_sent);
+ EXPECT_EQ(static_cast<int32_t>(kReportBlock.cumulative_num_packets_lost),
stats.packets_lost);
- EXPECT_EQ(Q8ToFloat(report_block.fraction_lost), stats.fraction_lost);
- EXPECT_EQ(std::string(codec_inst.plname), stats.codec_name);
- EXPECT_EQ(static_cast<int32_t>(report_block.extended_highest_sequence_number),
+ EXPECT_EQ(Q8ToFloat(kReportBlock.fraction_lost), stats.fraction_lost);
+ EXPECT_EQ(std::string(kCodecInst.plname), stats.codec_name);
+ EXPECT_EQ(static_cast<int32_t>(kReportBlock.extended_highest_sequence_number),
stats.ext_seqnum);
- EXPECT_EQ(static_cast<int32_t>(report_block.interarrival_jitter /
- (codec_inst.plfreq / 1000)), stats.jitter_ms);
- EXPECT_EQ(call_stats.rttMs, stats.rtt_ms);
- EXPECT_EQ(static_cast<int32_t>(FakeVoiceEngine::kSendSpeechInputLevel),
- stats.audio_level);
+ EXPECT_EQ(static_cast<int32_t>(kReportBlock.interarrival_jitter /
+ (kCodecInst.plfreq / 1000)),
+ stats.jitter_ms);
+ EXPECT_EQ(kCallStats.rttMs, stats.rtt_ms);
+ EXPECT_EQ(static_cast<int32_t>(kSpeechInputLevel), stats.audio_level);
EXPECT_EQ(-1, stats.aec_quality_min);
- EXPECT_EQ(FakeVoiceEngine::kSendEchoDelayMedian, stats.echo_delay_median_ms);
- EXPECT_EQ(FakeVoiceEngine::kSendEchoDelayStdDev, stats.echo_delay_std_ms);
- EXPECT_EQ(FakeVoiceEngine::kSendEchoReturnLoss, stats.echo_return_loss);
- EXPECT_EQ(FakeVoiceEngine::kSendEchoReturnLossEnhancement,
- stats.echo_return_loss_enhancement);
+ EXPECT_EQ(kEchoDelayMedian, stats.echo_delay_median_ms);
+ EXPECT_EQ(kEchoDelayStdDev, stats.echo_delay_std_ms);
+ EXPECT_EQ(kEchoReturnLoss, stats.echo_return_loss);
+ EXPECT_EQ(kEchoReturnLossEnhancement, stats.echo_return_loss_enhancement);
EXPECT_FALSE(stats.typing_noise_detected);
}
+
+TEST(AudioSendStreamTest, GetStatsTypingNoiseDetected) {
+ ConfigHelper helper;
+ internal::AudioSendStream send_stream(helper.config(), helper.audio_state(),
+ helper.congestion_controller());
+ helper.SetupMockForGetStats();
+ EXPECT_FALSE(send_stream.GetStats().typing_noise_detected);
+
+ internal::AudioState* internal_audio_state =
+ static_cast<internal::AudioState*>(helper.audio_state().get());
+ VoiceEngineObserver* voe_observer =
+ static_cast<VoiceEngineObserver*>(internal_audio_state);
+ voe_observer->CallbackOnError(-1, VE_TYPING_NOISE_WARNING);
+ EXPECT_TRUE(send_stream.GetStats().typing_noise_detected);
+ voe_observer->CallbackOnError(-1, VE_TYPING_NOISE_OFF_WARNING);
+ EXPECT_FALSE(send_stream.GetStats().typing_noise_detected);
+}
} // namespace test
} // namespace webrtc
diff --git a/webrtc/audio/audio_sink.h b/webrtc/audio/audio_sink.h
new file mode 100644
index 0000000000..999644f4ce
--- /dev/null
+++ b/webrtc/audio/audio_sink.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_AUDIO_AUDIO_SINK_H_
+#define WEBRTC_AUDIO_AUDIO_SINK_H_
+
+#if defined(WEBRTC_POSIX) && !defined(__STDC_FORMAT_MACROS)
+// Avoid conflict with format_macros.h.
+#define __STDC_FORMAT_MACROS
+#endif
+
+#include <inttypes.h>
+#include <stddef.h>
+
+namespace webrtc {
+
+// Represents a simple push audio sink.
+class AudioSinkInterface {
+ public:
+ virtual ~AudioSinkInterface() {}
+
+ struct Data {
+ Data(int16_t* data,
+ size_t samples_per_channel,
+ int sample_rate,
+ size_t channels,
+ uint32_t timestamp)
+ : data(data),
+ samples_per_channel(samples_per_channel),
+ sample_rate(sample_rate),
+ channels(channels),
+ timestamp(timestamp) {}
+
+ int16_t* data; // The actual 16bit audio data.
+ size_t samples_per_channel; // Number of frames in the buffer.
+ int sample_rate; // Sample rate in Hz.
+ size_t channels; // Number of channels in the audio data.
+ uint32_t timestamp; // The RTP timestamp of the first sample.
+ };
+
+ virtual void OnData(const Data& audio) = 0;
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_AUDIO_AUDIO_SINK_H_
diff --git a/webrtc/audio/audio_state.cc b/webrtc/audio/audio_state.cc
new file mode 100644
index 0000000000..e63f97af2d
--- /dev/null
+++ b/webrtc/audio/audio_state.cc
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/audio/audio_state.h"
+
+#include "webrtc/base/atomicops.h"
+#include "webrtc/base/checks.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/voice_engine/include/voe_errors.h"
+
+namespace webrtc {
+namespace internal {
+
+AudioState::AudioState(const AudioState::Config& config)
+ : config_(config), voe_base_(config.voice_engine) {
+ process_thread_checker_.DetachFromThread();
+ // Only one AudioState should be created per VoiceEngine.
+ RTC_CHECK(voe_base_->RegisterVoiceEngineObserver(*this) != -1);
+}
+
+AudioState::~AudioState() {
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+ voe_base_->DeRegisterVoiceEngineObserver();
+}
+
+VoiceEngine* AudioState::voice_engine() {
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+ return config_.voice_engine;
+}
+
+bool AudioState::typing_noise_detected() const {
+ RTC_DCHECK(thread_checker_.CalledOnValidThread());
+ rtc::CritScope lock(&crit_sect_);
+ return typing_noise_detected_;
+}
+
+// Reference count; implementation copied from rtc::RefCountedObject.
+int AudioState::AddRef() const {
+ return rtc::AtomicOps::Increment(&ref_count_);
+}
+
+// Reference count; implementation copied from rtc::RefCountedObject.
+int AudioState::Release() const {
+ int count = rtc::AtomicOps::Decrement(&ref_count_);
+ if (!count) {
+ delete this;
+ }
+ return count;
+}
+
+void AudioState::CallbackOnError(int channel_id, int err_code) {
+ RTC_DCHECK(process_thread_checker_.CalledOnValidThread());
+
+ // All call sites in VoE, as of this writing, specify -1 as channel_id.
+ RTC_DCHECK(channel_id == -1);
+ LOG(LS_INFO) << "VoiceEngine error " << err_code << " reported on channel "
+ << channel_id << ".";
+ if (err_code == VE_TYPING_NOISE_WARNING) {
+ rtc::CritScope lock(&crit_sect_);
+ typing_noise_detected_ = true;
+ } else if (err_code == VE_TYPING_NOISE_OFF_WARNING) {
+ rtc::CritScope lock(&crit_sect_);
+ typing_noise_detected_ = false;
+ }
+}
+} // namespace internal
+
+rtc::scoped_refptr<AudioState> AudioState::Create(
+ const AudioState::Config& config) {
+ return rtc::scoped_refptr<AudioState>(new internal::AudioState(config));
+}
+} // namespace webrtc
diff --git a/webrtc/audio/audio_state.h b/webrtc/audio/audio_state.h
new file mode 100644
index 0000000000..2cb83e4989
--- /dev/null
+++ b/webrtc/audio/audio_state.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_AUDIO_AUDIO_STATE_H_
+#define WEBRTC_AUDIO_AUDIO_STATE_H_
+
+#include "webrtc/audio_state.h"
+#include "webrtc/audio/scoped_voe_interface.h"
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/base/criticalsection.h"
+#include "webrtc/base/thread_checker.h"
+#include "webrtc/voice_engine/include/voe_base.h"
+
+namespace webrtc {
+namespace internal {
+
+class AudioState final : public webrtc::AudioState,
+ public webrtc::VoiceEngineObserver {
+ public:
+ explicit AudioState(const AudioState::Config& config);
+ ~AudioState() override;
+
+ VoiceEngine* voice_engine();
+ bool typing_noise_detected() const;
+
+ private:
+ // rtc::RefCountInterface implementation.
+ int AddRef() const override;
+ int Release() const override;
+
+ // webrtc::VoiceEngineObserver implementation.
+ void CallbackOnError(int channel_id, int err_code) override;
+
+ rtc::ThreadChecker thread_checker_;
+ rtc::ThreadChecker process_thread_checker_;
+ const webrtc::AudioState::Config config_;
+
+ // We hold one interface pointer to the VoE to make sure it is kept alive.
+ ScopedVoEInterface<VoEBase> voe_base_;
+
+ // The critical section isn't strictly needed in this case, but xSAN bots may
+ // trigger on unprotected cross-thread access.
+ mutable rtc::CriticalSection crit_sect_;
+ bool typing_noise_detected_ GUARDED_BY(crit_sect_) = false;
+
+ // Reference count; implementation copied from rtc::RefCountedObject.
+ mutable volatile int ref_count_ = 0;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(AudioState);
+};
+} // namespace internal
+} // namespace webrtc
+
+#endif // WEBRTC_AUDIO_AUDIO_STATE_H_
diff --git a/webrtc/audio/audio_state_unittest.cc b/webrtc/audio/audio_state_unittest.cc
new file mode 100644
index 0000000000..11fbdb4a86
--- /dev/null
+++ b/webrtc/audio/audio_state_unittest.cc
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "webrtc/audio/audio_state.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/test/mock_voice_engine.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+
+struct ConfigHelper {
+ ConfigHelper() {
+ EXPECT_CALL(voice_engine_,
+ RegisterVoiceEngineObserver(testing::_)).WillOnce(testing::Return(0));
+ EXPECT_CALL(voice_engine_,
+ DeRegisterVoiceEngineObserver()).WillOnce(testing::Return(0));
+ config_.voice_engine = &voice_engine_;
+ }
+ AudioState::Config& config() { return config_; }
+ MockVoiceEngine& voice_engine() { return voice_engine_; }
+
+ private:
+ testing::StrictMock<MockVoiceEngine> voice_engine_;
+ AudioState::Config config_;
+};
+} // namespace
+
+TEST(AudioStateTest, Create) {
+ ConfigHelper helper;
+ rtc::scoped_refptr<AudioState> audio_state =
+ AudioState::Create(helper.config());
+ EXPECT_TRUE(audio_state.get());
+}
+
+TEST(AudioStateTest, ConstructDestruct) {
+ ConfigHelper helper;
+ rtc::scoped_ptr<internal::AudioState> audio_state(
+ new internal::AudioState(helper.config()));
+}
+
+TEST(AudioStateTest, GetVoiceEngine) {
+ ConfigHelper helper;
+ rtc::scoped_ptr<internal::AudioState> audio_state(
+ new internal::AudioState(helper.config()));
+ EXPECT_EQ(audio_state->voice_engine(), &helper.voice_engine());
+}
+
+TEST(AudioStateTest, TypingNoiseDetected) {
+ ConfigHelper helper;
+ rtc::scoped_ptr<internal::AudioState> audio_state(
+ new internal::AudioState(helper.config()));
+ VoiceEngineObserver* voe_observer =
+ static_cast<VoiceEngineObserver*>(audio_state.get());
+ EXPECT_FALSE(audio_state->typing_noise_detected());
+
+ voe_observer->CallbackOnError(-1, VE_NOT_INITED);
+ EXPECT_FALSE(audio_state->typing_noise_detected());
+
+ voe_observer->CallbackOnError(-1, VE_TYPING_NOISE_WARNING);
+ EXPECT_TRUE(audio_state->typing_noise_detected());
+ voe_observer->CallbackOnError(-1, VE_NOT_INITED);
+ EXPECT_TRUE(audio_state->typing_noise_detected());
+
+ voe_observer->CallbackOnError(-1, VE_TYPING_NOISE_OFF_WARNING);
+ EXPECT_FALSE(audio_state->typing_noise_detected());
+ voe_observer->CallbackOnError(-1, VE_NOT_INITED);
+ EXPECT_FALSE(audio_state->typing_noise_detected());
+}
+} // namespace test
+} // namespace webrtc
diff --git a/webrtc/audio/webrtc_audio.gypi b/webrtc/audio/webrtc_audio.gypi
index b9d45db56d..53b7d16b1a 100644
--- a/webrtc/audio/webrtc_audio.gypi
+++ b/webrtc/audio/webrtc_audio.gypi
@@ -18,6 +18,9 @@
'audio/audio_receive_stream.h',
'audio/audio_send_stream.cc',
'audio/audio_send_stream.h',
+ 'audio/audio_sink.h',
+ 'audio/audio_state.cc',
+ 'audio/audio_state.h',
'audio/conversion.h',
'audio/scoped_voe_interface.h',
],