VoIP API implementation on top of AudioIngress/Egress

This is one last CL that includes the rest of VoIP API implementation.

Bug: webrtc:11251
Change-Id: I3f1b0bf2fd48be864ffc73482105f9514f75f9e0
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/173860
Commit-Queue: Tim Na <natim@webrtc.org>
Reviewed-by: Per Åhgren <peah@webrtc.org>
Reviewed-by: Karl Wiberg <kwiberg@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#31168}
This commit is contained in:
Tim Na 2020-05-05 11:03:54 -07:00 committed by Commit Bot
parent c064467b32
commit c0df5fc25b
24 changed files with 1449 additions and 222 deletions

View File

@ -687,8 +687,11 @@ if (rtc_include_tests) {
rtc_test("voip_unittests") { rtc_test("voip_unittests") {
testonly = true testonly = true
deps = [ deps = [
"api/voip:voip_engine_factory_unittests",
"audio/voip/test:audio_channel_unittests",
"audio/voip/test:audio_egress_unittests", "audio/voip/test:audio_egress_unittests",
"audio/voip/test:audio_ingress_unittests", "audio/voip/test:audio_ingress_unittests",
"audio/voip/test:voip_core_unittests",
"test:test_main", "test:test_main",
] ]
} }

View File

@ -1,10 +1,10 @@
#Copyright(c) 2020 The WebRTC project authors.All Rights Reserved. # Copyright(c) 2020 The WebRTC project authors.All Rights Reserved.
# #
#Use of this source code is governed by a BSD - style license # 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 # that can be found in the LICENSE file in the root of the source
#tree.An additional intellectual property rights grant can be found # tree.An additional intellectual property rights grant can be found
#in the file PATENTS.All contributing project authors may # in the file PATENTS.All contributing project authors may
#be found in the AUTHORS file in the root of the source tree. # be found in the AUTHORS file in the root of the source tree.
import("../../webrtc.gni") import("../../webrtc.gni")
@ -22,3 +22,36 @@ rtc_source_set("voip_api") {
"//third_party/abseil-cpp/absl/types:optional", "//third_party/abseil-cpp/absl/types:optional",
] ]
} }
rtc_library("voip_engine_factory") {
visibility = [ "*" ]
sources = [
"voip_engine_factory.cc",
"voip_engine_factory.h",
]
deps = [
":voip_api",
"..:scoped_refptr",
"../../audio/voip:voip_core",
"../../modules/audio_device:audio_device_api",
"../../modules/audio_processing:api",
"../../rtc_base:logging",
"../audio_codecs:audio_codecs_api",
"../task_queue",
]
}
if (rtc_include_tests) {
rtc_library("voip_engine_factory_unittests") {
testonly = true
sources = [ "voip_engine_factory_unittest.cc" ]
deps = [
":voip_engine_factory",
"../../modules/audio_device:mock_audio_device",
"../../modules/audio_processing:mocks",
"../../test:audio_codec_mocks",
"../../test:test_support",
"../task_queue:default_task_queue_factory",
]
}
}

View File

@ -2,4 +2,9 @@ specific_include_rules = {
".*\.h": [ ".*\.h": [
"+third_party/absl/types/optional.h", "+third_party/absl/types/optional.h",
], ],
}
"voip_engine_factory.h": [
"+modules/audio_device/include/audio_device.h",
"+modules/audio_processing/include/audio_processing.h",
],
}

View File

@ -1,17 +1,17 @@
// /*
// Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
// *
// Use of this source code is governed by a BSD-style license * 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 * that can be found in the LICENSE file in the root of the source
// tree. An additional intellectual property rights grant can be found * tree. An additional intellectual property rights grant can be found
// in the file PATENTS. All contributing project authors may * in the file PATENTS. All contributing project authors may
// be found in the AUTHORS file in the root of the source tree. * be found in the AUTHORS file in the root of the source tree.
// */
#ifndef API_VOIP_VOIP_BASE_H_ #ifndef API_VOIP_VOIP_BASE_H_
#define API_VOIP_VOIP_BASE_H_ #define API_VOIP_VOIP_BASE_H_
#include "third_party/absl/types/optional.h" #include "absl/types/optional.h"
namespace webrtc { namespace webrtc {
@ -51,13 +51,11 @@ class VoipBase {
Transport* transport, Transport* transport,
absl::optional<uint32_t> local_ssrc) = 0; absl::optional<uint32_t> local_ssrc) = 0;
// Releases |channel_id| that has served the purpose. // Releases |channel_id| that no longer has any use.
// Released channel will be re-allocated again that invoking operations
// on released |channel_id| will lead to undefined behavior.
virtual void ReleaseChannel(ChannelId channel_id) = 0; virtual void ReleaseChannel(ChannelId channel_id) = 0;
// Starts sending on |channel_id|. This will start microphone if first to // Starts sending on |channel_id|. This will start microphone if not started
// start. Returns false if initialization has failed on selected microphone // yet. Returns false if initialization has failed on selected microphone
// device. API is subject to expand to reflect error condition to application // device. API is subject to expand to reflect error condition to application
// later. // later.
virtual bool StartSend(ChannelId channel_id) = 0; virtual bool StartSend(ChannelId channel_id) = 0;

View File

@ -1,12 +1,12 @@
// /*
// Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
// *
// Use of this source code is governed by a BSD-style license * 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 * that can be found in the LICENSE file in the root of the source
// tree. An additional intellectual property rights grant can be found * tree. An additional intellectual property rights grant can be found
// in the file PATENTS. All contributing project authors may * in the file PATENTS. All contributing project authors may
// be found in the AUTHORS file in the root of the source tree. * be found in the AUTHORS file in the root of the source tree.
// */
#ifndef API_VOIP_VOIP_CODEC_H_ #ifndef API_VOIP_VOIP_CODEC_H_
#define API_VOIP_VOIP_CODEC_H_ #define API_VOIP_VOIP_CODEC_H_

View File

@ -1,12 +1,12 @@
// /*
// Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
// *
// Use of this source code is governed by a BSD-style license * 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 * that can be found in the LICENSE file in the root of the source
// tree. An additional intellectual property rights grant can be found * tree. An additional intellectual property rights grant can be found
// in the file PATENTS. All contributing project authors may * in the file PATENTS. All contributing project authors may
// be found in the AUTHORS file in the root of the source tree. * be found in the AUTHORS file in the root of the source tree.
// */
#ifndef API_VOIP_VOIP_ENGINE_H_ #ifndef API_VOIP_VOIP_ENGINE_H_
#define API_VOIP_VOIP_ENGINE_H_ #define API_VOIP_VOIP_ENGINE_H_
@ -17,50 +17,60 @@ class VoipBase;
class VoipCodec; class VoipCodec;
class VoipNetwork; class VoipNetwork;
// VoipEngine interfaces // VoipEngine is the main interface serving as the entry point for all VoIP
// APIs. A single instance of VoipEngine should suffice the most of the need for
// typical VoIP applications as it handles multiple media sessions including a
// specialized session type like ad-hoc mesh conferencing. Below example code
// describes the typical sequence of API usage. Each API header contains more
// description on what the methods are used for.
// //
// These pointer interfaces are valid as long as VoipEngine is available. // // Caller is responsible of setting desired audio components.
// Therefore, application must synchronize the usage within the life span of // VoipEngineConfig config;
// created VoipEngine instance. // config.encoder_factory = CreateBuiltinAudioEncoderFactory();
// config.decoder_factory = CreateBuiltinAudioDecoderFactory();
// config.task_queue_factory = CreateDefaultTaskQueueFactory();
// config.audio_device =
// AudioDeviceModule::Create(AudioDeviceModule::kPlatformDefaultAudio,
// config.task_queue_factory.get());
// config.audio_processing = AudioProcessingBuilder().Create();
// //
// auto voip_engine = // auto voip_engine = CreateVoipEngine(std::move(config));
// webrtc::VoipEngineBuilder() // if (!voip_engine) return some_failure;
// .SetAudioEncoderFactory(CreateBuiltinAudioEncoderFactory())
// .SetAudioDecoderFactory(CreateBuiltinAudioDecoderFactory())
// .Create();
// //
// auto voip_base = voip_engine->Base(); // auto& voip_base = voip_engine->Base();
// auto voip_codec = voip_engine->Codec(); // auto& voip_codec = voip_engine->Codec();
// auto voip_network = voip_engine->Network(); // auto& voip_network = voip_engine->Network();
// //
// VoipChannel::Config config = { &app_transport_, 0xdeadc0de }; // absl::optional<ChannelId> channel =
// int channel = voip_base.CreateChannel(config); // voip_base.CreateChannel(&app_transport_);
// if (!channel) return some_failure;
// //
// // After SDP offer/answer, payload type and codec usage have been // // After SDP offer/answer, set payload type and codecs that have been
// // decided through negotiation. // // decided through SDP negotiation.
// voip_codec.SetSendCodec(channel, ...); // voip_codec.SetSendCodec(*channel, ...);
// voip_codec.SetReceiveCodecs(channel, ...); // voip_codec.SetReceiveCodecs(*channel, ...);
// //
// // Start Send/Playout on voip channel. // // Start sending and playing RTP on voip channel.
// voip_base.StartSend(channel); // voip_base.StartSend(*channel);
// voip_base.StartPlayout(channel); // voip_base.StartPlayout(*channel);
// //
// // Inject received rtp/rtcp thru voip network interface. // // Inject received RTP/RTCP through VoipNetwork interface.
// voip_network.ReceivedRTPPacket(channel, rtp_data, rtp_size); // voip_network.ReceivedRTPPacket(*channel, ...);
// voip_network.ReceivedRTCPPacket(channel, rtcp_data, rtcp_size); // voip_network.ReceivedRTCPPacket(*channel, ...);
// //
// // Stop and release voip channel. // // Stop and release voip channel.
// voip_base.StopSend(channel); // voip_base.StopSend(*channel);
// voip_base.StopPlayout(channel); // voip_base.StopPlayout(*channel);
// // voip_base.ReleaseChannel(*channel);
// voip_base.ReleaseChannel(channel);
// //
// Current VoipEngine defines three sub-API classes and is subject to expand in
// near future.
class VoipEngine { class VoipEngine {
public: public:
virtual ~VoipEngine() = default; virtual ~VoipEngine() = default;
// VoipBase is the audio session management interface that // VoipBase is the audio session management interface that
// create/release/start/stop one-to-one audio media session. // creates/releases/starts/stops an one-to-one audio media session.
virtual VoipBase& Base() = 0; virtual VoipBase& Base() = 0;
// VoipNetwork provides injection APIs that would enable application // VoipNetwork provides injection APIs that would enable application

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2020 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 "api/voip/voip_engine_factory.h"
#include <utility>
#include "audio/voip/voip_core.h"
#include "rtc_base/logging.h"
namespace webrtc {
std::unique_ptr<VoipEngine> CreateVoipEngine(VoipEngineConfig config) {
RTC_CHECK(config.encoder_factory);
RTC_CHECK(config.decoder_factory);
RTC_CHECK(config.task_queue_factory);
RTC_CHECK(config.audio_device_module);
if (!config.audio_processing) {
RTC_DLOG(INFO) << "No audio processing functionality provided.";
}
auto voip_core = std::make_unique<VoipCore>();
if (!voip_core->Init(std::move(config.encoder_factory),
std::move(config.decoder_factory),
std::move(config.task_queue_factory),
std::move(config.audio_device_module),
std::move(config.audio_processing))) {
RTC_DLOG(LS_ERROR) << "Failed to initialize VoIP core.";
return nullptr;
}
return voip_core;
}
} // namespace webrtc

View File

@ -0,0 +1,71 @@
/*
* Copyright (c) 2020 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 API_VOIP_VOIP_ENGINE_FACTORY_H_
#define API_VOIP_VOIP_ENGINE_FACTORY_H_
#include <memory>
#include "api/audio_codecs/audio_decoder_factory.h"
#include "api/audio_codecs/audio_encoder_factory.h"
#include "api/scoped_refptr.h"
#include "api/task_queue/task_queue_factory.h"
#include "api/voip/voip_engine.h"
#include "modules/audio_device/include/audio_device.h"
#include "modules/audio_processing/include/audio_processing.h"
namespace webrtc {
// VoipEngineConfig is a struct that defines parameters to instantiate a
// VoipEngine instance through CreateVoipEngine factory method. Each member is
// marked with comments as either mandatory or optional and default
// implementations that applications can use.
struct VoipEngineConfig {
// Mandatory (e.g. api/audio_codec/builtin_audio_encoder_factory).
// AudioEncoderFactory provides a set of audio codecs for VoipEngine to encode
// the audio input sample. Application can choose to limit the set to reduce
// application footprint.
rtc::scoped_refptr<AudioEncoderFactory> encoder_factory;
// Mandatory (e.g. api/audio_codec/builtin_audio_decoder_factory).
// AudioDecoderFactory provides a set of audio codecs for VoipEngine to decode
// the received RTP packets from remote media endpoint. Application can choose
// to limit the set to reduce application footprint.
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory;
// Mandatory (e.g. api/task_queue/default_task_queue_factory).
// TaskQeueuFactory provided for VoipEngine to work asynchronously on its
// encoding flow.
std::unique_ptr<TaskQueueFactory> task_queue_factory;
// Mandatory (e.g. modules/audio_device/include).
// AudioDeviceModule that periocally provides audio input samples from
// recording device (e.g. microphone) and requests audio output samples to
// play through its output device (e.g. speaker).
rtc::scoped_refptr<AudioDeviceModule> audio_device_module;
// Optional (e.g. modules/audio_processing/include).
// AudioProcessing provides audio procesing functionalities (e.g. acoustic
// echo cancellation, noise suppression, gain control, etc) on audio input
// samples for VoipEngine. When optionally not set, VoipEngine will not have
// such functionalities to perform on audio input samples received from
// AudioDeviceModule.
rtc::scoped_refptr<AudioProcessing> audio_processing;
};
// Creates a VoipEngine instance with provided VoipEngineConfig.
// This could return nullptr if AudioDeviceModule (ADM) initialization fails
// during construction of VoipEngine which would render VoipEngine
// nonfunctional.
std::unique_ptr<VoipEngine> CreateVoipEngine(VoipEngineConfig config);
} // namespace webrtc
#endif // API_VOIP_VOIP_ENGINE_FACTORY_H_

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2020 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 <utility>
#include "api/task_queue/default_task_queue_factory.h"
#include "api/voip/voip_engine_factory.h"
#include "modules/audio_device/include/mock_audio_device.h"
#include "modules/audio_processing/include/mock_audio_processing.h"
#include "test/gtest.h"
#include "test/mock_audio_decoder_factory.h"
#include "test/mock_audio_encoder_factory.h"
namespace webrtc {
namespace {
// Create voip engine with mock modules as normal use case.
TEST(VoipEngineFactoryTest, CreateEngineWithMockModules) {
VoipEngineConfig config;
config.encoder_factory = new rtc::RefCountedObject<MockAudioEncoderFactory>();
config.decoder_factory = new rtc::RefCountedObject<MockAudioDecoderFactory>();
config.task_queue_factory = CreateDefaultTaskQueueFactory();
config.audio_processing =
new rtc::RefCountedObject<test::MockAudioProcessing>();
config.audio_device_module = test::MockAudioDeviceModule::CreateNice();
auto voip_engine = CreateVoipEngine(std::move(config));
EXPECT_NE(voip_engine, nullptr);
}
// Create voip engine without setting audio processing as optional component.
TEST(VoipEngineFactoryTest, UseNoAudioProcessing) {
VoipEngineConfig config;
config.encoder_factory = new rtc::RefCountedObject<MockAudioEncoderFactory>();
config.decoder_factory = new rtc::RefCountedObject<MockAudioDecoderFactory>();
config.task_queue_factory = CreateDefaultTaskQueueFactory();
config.audio_device_module = test::MockAudioDeviceModule::CreateNice();
auto voip_engine = CreateVoipEngine(std::move(config));
EXPECT_NE(voip_engine, nullptr);
}
} // namespace
} // namespace webrtc

View File

@ -1,12 +1,12 @@
// /*
// Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
// *
// Use of this source code is governed by a BSD-style license * 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 * that can be found in the LICENSE file in the root of the source
// tree. An additional intellectual property rights grant can be found * tree. An additional intellectual property rights grant can be found
// in the file PATENTS. All contributing project authors may * in the file PATENTS. All contributing project authors may
// be found in the AUTHORS file in the root of the source tree. * be found in the AUTHORS file in the root of the source tree.
// */
#ifndef API_VOIP_VOIP_NETWORK_H_ #ifndef API_VOIP_VOIP_NETWORK_H_
#define API_VOIP_VOIP_NETWORK_H_ #define API_VOIP_VOIP_NETWORK_H_
@ -16,24 +16,24 @@
namespace webrtc { namespace webrtc {
// VoipNetwork interface currently provides any network related interface // VoipNetwork interface provides any network related interfaces such as
// such as processing received RTP/RTCP packet from remote endpoint. // processing received RTP/RTCP packet from remote endpoint. This interface
// The interface subject to expand as needed. // requires a ChannelId created via VoipBase interface. Note that using invalid
// // (previously released) ChannelId will silently fail these API calls as it
// This interface requires a channel handle created via VoipBase interface. // would have released underlying audio components. It's anticipated that caller
// may be using different thread for network I/O where released channel id is
// still used to input incoming RTP packets in which case we should silently
// ignore. The interface is subjected to expand as needed in near future.
class VoipNetwork { class VoipNetwork {
public: public:
// The packets received from the network should be passed to this // The data received from the network including RTP header is passed here.
// function. Note that the data including the RTP-header must also be
// given to the VoipEngine.
virtual void ReceivedRTPPacket(ChannelId channel_id, virtual void ReceivedRTPPacket(ChannelId channel_id,
rtc::ArrayView<const uint8_t> data) = 0; rtc::ArrayView<const uint8_t> rtp_packet) = 0;
// The packets received from the network should be passed to this // The data received from the network including RTCP header is passed here.
// function. Note that the data including the RTCP-header must also be virtual void ReceivedRTCPPacket(
// given to the VoipEngine. ChannelId channel_id,
virtual void ReceivedRTCPPacket(ChannelId channel_id, rtc::ArrayView<const uint8_t> rtcp_packet) = 0;
rtc::ArrayView<const uint8_t> data) = 0;
protected: protected:
virtual ~VoipNetwork() = default; virtual ~VoipNetwork() = default;

View File

@ -8,20 +8,64 @@
import("../../webrtc.gni") import("../../webrtc.gni")
rtc_library("voip_core") {
sources = [
"voip_core.cc",
"voip_core.h",
]
deps = [
":audio_channel",
"..:audio",
"../../api:scoped_refptr",
"../../api/audio_codecs:audio_codecs_api",
"../../api/task_queue",
"../../api/voip:voip_api",
"../../modules/audio_device:audio_device_api",
"../../modules/audio_mixer:audio_mixer_impl",
"../../modules/audio_processing:api",
"../../modules/utility:utility",
"../../rtc_base:criticalsection",
"../../rtc_base:logging",
"//third_party/abseil-cpp/absl/types:optional",
]
}
rtc_library("audio_channel") {
sources = [
"audio_channel.cc",
"audio_channel.h",
]
deps = [
":audio_egress",
":audio_ingress",
"../../api:transport_api",
"../../api/audio_codecs:audio_codecs_api",
"../../api/task_queue",
"../../api/voip:voip_api",
"../../modules/audio_device:audio_device_api",
"../../modules/rtp_rtcp",
"../../modules/rtp_rtcp:rtp_rtcp_format",
"../../modules/utility",
"../../rtc_base:criticalsection",
"../../rtc_base:logging",
"../../rtc_base:refcount",
"../../rtc_base:rtc_base_approved",
]
}
rtc_library("audio_ingress") { rtc_library("audio_ingress") {
sources = [ sources = [
"audio_ingress.cc", "audio_ingress.cc",
"audio_ingress.h", "audio_ingress.h",
] ]
deps = [ deps = [
"..:audio",
"../../api:array_view", "../../api:array_view",
"../../api:rtp_headers", "../../api:rtp_headers",
"../../api:scoped_refptr", "../../api:scoped_refptr",
"../../api:transport_api", "../../api:transport_api",
"../../api/audio:audio_mixer_api", "../../api/audio:audio_mixer_api",
"../../api/audio_codecs:audio_codecs_api", "../../api/audio_codecs:audio_codecs_api",
"../../audio",
"../../audio/utility:audio_frame_operations",
"../../modules/audio_coding", "../../modules/audio_coding",
"../../modules/rtp_rtcp", "../../modules/rtp_rtcp",
"../../modules/rtp_rtcp:rtp_rtcp_format", "../../modules/rtp_rtcp:rtp_rtcp_format",
@ -30,6 +74,7 @@ rtc_library("audio_ingress") {
"../../rtc_base:logging", "../../rtc_base:logging",
"../../rtc_base:safe_minmax", "../../rtc_base:safe_minmax",
"../../rtc_base:timeutils", "../../rtc_base:timeutils",
"../utility:audio_frame_operations",
] ]
} }
@ -39,10 +84,9 @@ rtc_library("audio_egress") {
"audio_egress.h", "audio_egress.h",
] ]
deps = [ deps = [
"..:audio",
"../../api/audio_codecs:audio_codecs_api", "../../api/audio_codecs:audio_codecs_api",
"../../api/task_queue", "../../api/task_queue",
"../../audio",
"../../audio/utility:audio_frame_operations",
"../../call:audio_sender_interface", "../../call:audio_sender_interface",
"../../modules/audio_coding", "../../modules/audio_coding",
"../../modules/rtp_rtcp", "../../modules/rtp_rtcp",
@ -51,5 +95,6 @@ rtc_library("audio_egress") {
"../../rtc_base:rtc_task_queue", "../../rtc_base:rtc_task_queue",
"../../rtc_base:thread_checker", "../../rtc_base:thread_checker",
"../../rtc_base:timeutils", "../../rtc_base:timeutils",
"../utility:audio_frame_operations",
] ]
} }

126
audio/voip/audio_channel.cc Normal file
View File

@ -0,0 +1,126 @@
/*
* Copyright (c) 2020 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 "audio/voip/audio_channel.h"
#include <utility>
#include <vector>
#include "api/audio_codecs/audio_format.h"
#include "api/task_queue/task_queue_factory.h"
#include "modules/rtp_rtcp/include/receive_statistics.h"
#include "rtc_base/critical_section.h"
#include "rtc_base/location.h"
#include "rtc_base/logging.h"
namespace webrtc {
namespace {
constexpr int kRtcpReportIntervalMs = 5000;
} // namespace
AudioChannel::AudioChannel(
Transport* transport,
uint32_t local_ssrc,
TaskQueueFactory* task_queue_factory,
ProcessThread* process_thread,
AudioMixer* audio_mixer,
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory)
: audio_mixer_(audio_mixer), process_thread_(process_thread) {
RTC_DCHECK(task_queue_factory);
RTC_DCHECK(process_thread);
RTC_DCHECK(audio_mixer);
Clock* clock = Clock::GetRealTimeClock();
receive_statistics_ = ReceiveStatistics::Create(clock);
RtpRtcp::Configuration rtp_config;
rtp_config.clock = clock;
rtp_config.audio = true;
rtp_config.receive_statistics = receive_statistics_.get();
rtp_config.rtcp_report_interval_ms = kRtcpReportIntervalMs;
rtp_config.outgoing_transport = transport;
rtp_config.local_media_ssrc = local_ssrc;
rtp_rtcp_ = RtpRtcp::Create(rtp_config);
rtp_rtcp_->SetSendingMediaStatus(false);
rtp_rtcp_->SetRTCPStatus(RtcpMode::kCompound);
// ProcessThread periodically services RTP stack for RTCP.
process_thread_->RegisterModule(rtp_rtcp_.get(), RTC_FROM_HERE);
ingress_ = std::make_unique<AudioIngress>(rtp_rtcp_.get(), clock,
receive_statistics_.get(),
std::move(decoder_factory));
egress_ =
std::make_unique<AudioEgress>(rtp_rtcp_.get(), clock, task_queue_factory);
// Set the instance of audio ingress to be part of audio mixer for ADM to
// fetch audio samples to play.
audio_mixer_->AddSource(ingress_.get());
}
AudioChannel::~AudioChannel() {
if (egress_->IsSending()) {
StopSend();
}
if (ingress_->IsPlaying()) {
StopPlay();
}
audio_mixer_->RemoveSource(ingress_.get());
process_thread_->DeRegisterModule(rtp_rtcp_.get());
}
void AudioChannel::StartSend() {
egress_->StartSend();
// Start sending with RTP stack if it has not been sending yet.
if (!rtp_rtcp_->Sending() && rtp_rtcp_->SetSendingStatus(true) != 0) {
RTC_DLOG(LS_ERROR) << "StartSend() RTP/RTCP failed to start sending";
}
}
void AudioChannel::StopSend() {
egress_->StopSend();
// If the channel is not playing and RTP stack is active then deactivate RTP
// stack. SetSendingStatus(false) triggers the transmission of RTCP BYE
// message to remote endpoint.
if (!IsPlaying() && rtp_rtcp_->Sending() &&
rtp_rtcp_->SetSendingStatus(false) != 0) {
RTC_DLOG(LS_ERROR) << "StopSend() RTP/RTCP failed to stop sending";
}
}
void AudioChannel::StartPlay() {
ingress_->StartPlay();
// If RTP stack is not sending then start sending as in recv-only mode, RTCP
// receiver report is expected.
if (!rtp_rtcp_->Sending() && rtp_rtcp_->SetSendingStatus(true) != 0) {
RTC_DLOG(LS_ERROR) << "StartPlay() RTP/RTCP failed to start sending";
}
}
void AudioChannel::StopPlay() {
ingress_->StopPlay();
// Deactivate RTP stack only when both sending and receiving are stopped.
if (!IsSendingMedia() && rtp_rtcp_->Sending() &&
rtp_rtcp_->SetSendingStatus(false) != 0) {
RTC_DLOG(LS_ERROR) << "StopPlay() RTP/RTCP failed to stop sending";
}
}
} // namespace webrtc

View File

@ -0,0 +1,98 @@
/*
* Copyright (c) 2020 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 AUDIO_VOIP_AUDIO_CHANNEL_H_
#define AUDIO_VOIP_AUDIO_CHANNEL_H_
#include <map>
#include <memory>
#include <queue>
#include <utility>
#include "api/task_queue/task_queue_factory.h"
#include "api/voip/voip_base.h"
#include "audio/voip/audio_egress.h"
#include "audio/voip/audio_ingress.h"
#include "modules/rtp_rtcp/include/rtp_rtcp.h"
#include "modules/utility/include/process_thread.h"
#include "rtc_base/critical_section.h"
#include "rtc_base/ref_count.h"
namespace webrtc {
// AudioChannel represents a single media session and provides APIs over
// AudioIngress and AudioEgress. Note that a single RTP stack is shared with
// these two classes as it has both sending and receiving capabilities.
class AudioChannel : public rtc::RefCountInterface {
public:
AudioChannel(Transport* transport,
uint32_t local_ssrc,
TaskQueueFactory* task_queue_factory,
ProcessThread* process_thread,
AudioMixer* audio_mixer,
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory);
~AudioChannel() override;
// Set and get ChannelId that this audio channel belongs for debugging and
// logging purpose.
void SetId(ChannelId id) { id_ = id; }
ChannelId GetId() const { return id_; }
// APIs to start/stop audio channel on each direction.
void StartSend();
void StopSend();
void StartPlay();
void StopPlay();
// APIs relayed to AudioEgress.
bool IsSendingMedia() const { return egress_->IsSending(); }
AudioSender* GetAudioSender() { return egress_.get(); }
void SetEncoder(int payload_type,
const SdpAudioFormat& encoder_format,
std::unique_ptr<AudioEncoder> encoder) {
egress_->SetEncoder(payload_type, encoder_format, std::move(encoder));
}
absl::optional<SdpAudioFormat> GetEncoderFormat() const {
return egress_->GetEncoderFormat();
}
// APIs relayed to AudioIngress.
bool IsPlaying() const { return ingress_->IsPlaying(); }
void ReceivedRTPPacket(rtc::ArrayView<const uint8_t> rtp_packet) {
ingress_->ReceivedRTPPacket(rtp_packet);
}
void ReceivedRTCPPacket(rtc::ArrayView<const uint8_t> rtcp_packet) {
ingress_->ReceivedRTCPPacket(rtcp_packet);
}
void SetReceiveCodecs(const std::map<int, SdpAudioFormat>& codecs) {
ingress_->SetReceiveCodecs(codecs);
}
private:
// ChannelId that this audio channel belongs for logging purpose.
ChannelId id_;
// Synchronization is handled internally by AudioMixer.
AudioMixer* audio_mixer_;
// Synchronization is handled internally by ProcessThread.
ProcessThread* process_thread_;
// Listed in order for safe destruction of AudioChannel object.
// Synchronization for these are handled internally.
std::unique_ptr<ReceiveStatistics> receive_statistics_;
std::unique_ptr<RtpRtcp> rtp_rtcp_;
std::unique_ptr<AudioIngress> ingress_;
std::unique_ptr<AudioEgress> egress_;
};
} // namespace webrtc
#endif // AUDIO_VOIP_AUDIO_CHANNEL_H_

View File

@ -34,18 +34,16 @@ AudioEgress::~AudioEgress() {
} }
bool AudioEgress::IsSending() const { bool AudioEgress::IsSending() const {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
return rtp_rtcp_->SendingMedia(); return rtp_rtcp_->SendingMedia();
} }
void AudioEgress::SetEncoder(int payload_type, void AudioEgress::SetEncoder(int payload_type,
const SdpAudioFormat& encoder_format, const SdpAudioFormat& encoder_format,
std::unique_ptr<AudioEncoder> encoder) { std::unique_ptr<AudioEncoder> encoder) {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
RTC_DCHECK_GE(payload_type, 0); RTC_DCHECK_GE(payload_type, 0);
RTC_DCHECK_LE(payload_type, 127); RTC_DCHECK_LE(payload_type, 127);
encoder_format_ = encoder_format; SetEncoderFormat(encoder_format);
// The RTP/RTCP module needs to know the RTP timestamp rate (i.e. clockrate) // The RTP/RTCP module needs to know the RTP timestamp rate (i.e. clockrate)
// as well as some other things, so we collect this info and send it along. // as well as some other things, so we collect this info and send it along.
@ -58,20 +56,11 @@ void AudioEgress::SetEncoder(int payload_type,
audio_coding_->SetEncoder(std::move(encoder)); audio_coding_->SetEncoder(std::move(encoder));
} }
absl::optional<SdpAudioFormat> AudioEgress::GetEncoderFormat() const {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
return encoder_format_;
}
void AudioEgress::StartSend() { void AudioEgress::StartSend() {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
rtp_rtcp_->SetSendingMediaStatus(true); rtp_rtcp_->SetSendingMediaStatus(true);
} }
void AudioEgress::StopSend() { void AudioEgress::StopSend() {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
rtp_rtcp_->SetSendingMediaStatus(false); rtp_rtcp_->SetSendingMediaStatus(false);
} }
@ -144,7 +133,6 @@ int32_t AudioEgress::SendData(AudioFrameType frame_type,
void AudioEgress::RegisterTelephoneEventType(int rtp_payload_type, void AudioEgress::RegisterTelephoneEventType(int rtp_payload_type,
int sample_rate_hz) { int sample_rate_hz) {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
RTC_DCHECK_GE(rtp_payload_type, 0); RTC_DCHECK_GE(rtp_payload_type, 0);
RTC_DCHECK_LE(rtp_payload_type, 127); RTC_DCHECK_LE(rtp_payload_type, 127);
@ -154,7 +142,6 @@ void AudioEgress::RegisterTelephoneEventType(int rtp_payload_type,
} }
bool AudioEgress::SendTelephoneEvent(int dtmf_event, int duration_ms) { bool AudioEgress::SendTelephoneEvent(int dtmf_event, int duration_ms) {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
RTC_DCHECK_GE(dtmf_event, 0); RTC_DCHECK_GE(dtmf_event, 0);
RTC_DCHECK_LE(dtmf_event, 255); RTC_DCHECK_LE(dtmf_event, 255);
RTC_DCHECK_GE(duration_ms, 0); RTC_DCHECK_GE(duration_ms, 0);
@ -175,8 +162,6 @@ bool AudioEgress::SendTelephoneEvent(int dtmf_event, int duration_ms) {
} }
void AudioEgress::SetMute(bool mute) { void AudioEgress::SetMute(bool mute) {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
encoder_queue_.PostTask([this, mute] { encoder_queue_.PostTask([this, mute] {
RTC_DCHECK_RUN_ON(&encoder_queue_); RTC_DCHECK_RUN_ON(&encoder_queue_);
encoder_context_.mute_ = mute; encoder_context_.mute_ = mute;

View File

@ -34,10 +34,9 @@ namespace webrtc {
// encoded payload will be packetized by the RTP stack, resulting in ready to // encoded payload will be packetized by the RTP stack, resulting in ready to
// send RTP packet to remote endpoint. // send RTP packet to remote endpoint.
// //
// This class enforces single worker thread access by caller via SequenceChecker // TaskQueue is used to encode and send RTP asynchrounously as some OS platform
// in debug mode as expected thread usage pattern. In order to minimize the hold // uses the same thread for both audio input and output sample deliveries which
// on audio input thread from OS, TaskQueue is employed to encode and send RTP // can affect audio quality.
// asynchrounously.
// //
// Note that this class is originally based on ChannelSend in // Note that this class is originally based on ChannelSend in
// audio/channel_send.cc with non-audio related logic trimmed as aimed for // audio/channel_send.cc with non-audio related logic trimmed as aimed for
@ -72,7 +71,10 @@ class AudioEgress : public AudioSender, public AudioPacketizationCallback {
// Retrieve current encoder format info. This returns encoder format set // Retrieve current encoder format info. This returns encoder format set
// by SetEncoder() and if encoder is not set, this will return nullopt. // by SetEncoder() and if encoder is not set, this will return nullopt.
absl::optional<SdpAudioFormat> GetEncoderFormat() const; absl::optional<SdpAudioFormat> GetEncoderFormat() const {
rtc::CritScope lock(&lock_);
return encoder_format_;
}
// Register the payload type and sample rate for DTMF (RFC 4733) payload. // Register the payload type and sample rate for DTMF (RFC 4733) payload.
void RegisterTelephoneEventType(int rtp_payload_type, int sample_rate_hz); void RegisterTelephoneEventType(int rtp_payload_type, int sample_rate_hz);
@ -96,12 +98,15 @@ class AudioEgress : public AudioSender, public AudioPacketizationCallback {
size_t payload_size) override; size_t payload_size) override;
private: private:
// Ensure that single worker thread access. void SetEncoderFormat(const SdpAudioFormat& encoder_format) {
SequenceChecker worker_thread_checker_; rtc::CritScope lock(&lock_);
encoder_format_ = encoder_format;
}
rtc::CriticalSection lock_;
// Current encoder format selected by caller. // Current encoder format selected by caller.
absl::optional<SdpAudioFormat> encoder_format_ absl::optional<SdpAudioFormat> encoder_format_ RTC_GUARDED_BY(lock_);
RTC_GUARDED_BY(worker_thread_checker_);
// Synchronization is handled internally by RtpRtcp. // Synchronization is handled internally by RtpRtcp.
RtpRtcp* const rtp_rtcp_; RtpRtcp* const rtp_rtcp_;

View File

@ -38,27 +38,18 @@ AudioCodingModule::Config CreateAcmConfig(
AudioIngress::AudioIngress( AudioIngress::AudioIngress(
RtpRtcp* rtp_rtcp, RtpRtcp* rtp_rtcp,
Clock* clock, Clock* clock,
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory, ReceiveStatistics* receive_statistics,
std::unique_ptr<ReceiveStatistics> receive_statistics) rtc::scoped_refptr<AudioDecoderFactory> decoder_factory)
: playing_(false), : playing_(false),
remote_ssrc_(0), remote_ssrc_(0),
first_rtp_timestamp_(-1), first_rtp_timestamp_(-1),
rtp_receive_statistics_(std::move(receive_statistics)), rtp_receive_statistics_(receive_statistics),
rtp_rtcp_(rtp_rtcp), rtp_rtcp_(rtp_rtcp),
acm_receiver_(CreateAcmConfig(decoder_factory)), acm_receiver_(CreateAcmConfig(decoder_factory)),
ntp_estimator_(clock) {} ntp_estimator_(clock) {}
AudioIngress::~AudioIngress() = default; AudioIngress::~AudioIngress() = default;
void AudioIngress::StartPlay() {
playing_ = true;
}
void AudioIngress::StopPlay() {
playing_ = false;
output_audio_level_.ResetLevelFullRange();
}
AudioMixer::Source::AudioFrameInfo AudioIngress::GetAudioFrameWithInfo( AudioMixer::Source::AudioFrameInfo AudioIngress::GetAudioFrameWithInfo(
int sampling_rate, int sampling_rate,
AudioFrame* audio_frame) { AudioFrame* audio_frame) {
@ -113,17 +104,6 @@ AudioMixer::Source::AudioFrameInfo AudioIngress::GetAudioFrameWithInfo(
: AudioMixer::Source::AudioFrameInfo::kNormal; : AudioMixer::Source::AudioFrameInfo::kNormal;
} }
int AudioIngress::Ssrc() const {
return rtc::dchecked_cast<int>(remote_ssrc_.load());
}
int AudioIngress::PreferredSampleRate() const {
// Return the bigger of playout and receive frequency in the ACM. Note that
// return 0 means anything higher shouldn't cause any quality loss.
return std::max(acm_receiver_.last_packet_sample_rate_hz().value_or(0),
acm_receiver_.last_output_sample_rate_hz());
}
void AudioIngress::SetReceiveCodecs( void AudioIngress::SetReceiveCodecs(
const std::map<int, SdpAudioFormat>& codecs) { const std::map<int, SdpAudioFormat>& codecs) {
{ {
@ -135,36 +115,37 @@ void AudioIngress::SetReceiveCodecs(
acm_receiver_.SetCodecs(codecs); acm_receiver_.SetCodecs(codecs);
} }
void AudioIngress::ReceivedRTPPacket(const uint8_t* data, size_t length) { void AudioIngress::ReceivedRTPPacket(rtc::ArrayView<const uint8_t> rtp_packet) {
if (!Playing()) { if (!IsPlaying()) {
return; return;
} }
RtpPacketReceived rtp_packet; RtpPacketReceived rtp_packet_received;
rtp_packet.Parse(data, length); rtp_packet_received.Parse(rtp_packet.data(), rtp_packet.size());
// Set payload type's sampling rate before we feed it into ReceiveStatistics. // Set payload type's sampling rate before we feed it into ReceiveStatistics.
{ {
rtc::CritScope lock(&lock_); rtc::CritScope lock(&lock_);
const auto& it = receive_codec_info_.find(rtp_packet.PayloadType()); const auto& it =
receive_codec_info_.find(rtp_packet_received.PayloadType());
// If sampling rate info is not available in our received codec set, it // If sampling rate info is not available in our received codec set, it
// would mean that remote media endpoint is sending incorrect payload id // would mean that remote media endpoint is sending incorrect payload id
// which can't be processed correctly especially on payload type id in // which can't be processed correctly especially on payload type id in
// dynamic range. // dynamic range.
if (it == receive_codec_info_.end()) { if (it == receive_codec_info_.end()) {
RTC_DLOG(LS_WARNING) << "Unexpected payload id received: " RTC_DLOG(LS_WARNING) << "Unexpected payload id received: "
<< rtp_packet.PayloadType(); << rtp_packet_received.PayloadType();
return; return;
} }
rtp_packet.set_payload_type_frequency(it->second); rtp_packet_received.set_payload_type_frequency(it->second);
} }
rtp_receive_statistics_->OnRtpPacket(rtp_packet); rtp_receive_statistics_->OnRtpPacket(rtp_packet_received);
RTPHeader header; RTPHeader header;
rtp_packet.GetHeader(&header); rtp_packet_received.GetHeader(&header);
size_t packet_length = rtp_packet.size(); size_t packet_length = rtp_packet_received.size();
if (packet_length < header.headerLength || if (packet_length < header.headerLength ||
(packet_length - header.headerLength) < header.paddingLength) { (packet_length - header.headerLength) < header.paddingLength) {
RTC_DLOG(LS_ERROR) << "Packet length(" << packet_length << ") header(" RTC_DLOG(LS_ERROR) << "Packet length(" << packet_length << ") header("
@ -173,7 +154,7 @@ void AudioIngress::ReceivedRTPPacket(const uint8_t* data, size_t length) {
return; return;
} }
const uint8_t* payload = rtp_packet.data() + header.headerLength; const uint8_t* payload = rtp_packet_received.data() + header.headerLength;
size_t payload_length = packet_length - header.headerLength; size_t payload_length = packet_length - header.headerLength;
size_t payload_data_length = payload_length - header.paddingLength; size_t payload_data_length = payload_length - header.paddingLength;
auto data_view = rtc::ArrayView<const uint8_t>(payload, payload_data_length); auto data_view = rtc::ArrayView<const uint8_t>(payload, payload_data_length);
@ -185,9 +166,10 @@ void AudioIngress::ReceivedRTPPacket(const uint8_t* data, size_t length) {
} }
} }
void AudioIngress::ReceivedRTCPPacket(const uint8_t* data, size_t length) { void AudioIngress::ReceivedRTCPPacket(
// Deliver RTCP packet to RTP/RTCP module for parsing rtc::ArrayView<const uint8_t> rtcp_packet) {
rtp_rtcp_->IncomingRtcpPacket(data, length); // Deliver RTCP packet to RTP/RTCP module for parsing.
rtp_rtcp_->IncomingRtcpPacket(rtcp_packet.data(), rtcp_packet.size());
int64_t rtt = GetRoundTripTime(); int64_t rtt = GetRoundTripTime();
if (rtt == -1) { if (rtt == -1) {
@ -234,24 +216,4 @@ int64_t AudioIngress::GetRoundTripTime() {
return (block_data.has_rtt() ? block_data.last_rtt_ms() : -1); return (block_data.has_rtt() ? block_data.last_rtt_ms() : -1);
} }
int AudioIngress::GetSpeechOutputLevelFullRange() const {
return output_audio_level_.LevelFullRange();
}
bool AudioIngress::Playing() const {
return playing_;
}
NetworkStatistics AudioIngress::GetNetworkStatistics() const {
NetworkStatistics stats;
acm_receiver_.GetNetworkStatistics(&stats);
return stats;
}
AudioDecodingCallStats AudioIngress::GetDecodingStatistics() const {
AudioDecodingCallStats stats;
acm_receiver_.GetDecodingCallStatistics(&stats);
return stats;
}
} // namespace webrtc } // namespace webrtc

View File

@ -11,6 +11,7 @@
#ifndef AUDIO_VOIP_AUDIO_INGRESS_H_ #ifndef AUDIO_VOIP_AUDIO_INGRESS_H_
#define AUDIO_VOIP_AUDIO_INGRESS_H_ #define AUDIO_VOIP_AUDIO_INGRESS_H_
#include <algorithm>
#include <atomic> #include <atomic>
#include <map> #include <map>
#include <memory> #include <memory>
@ -45,47 +46,68 @@ class AudioIngress : public AudioMixer::Source {
public: public:
AudioIngress(RtpRtcp* rtp_rtcp, AudioIngress(RtpRtcp* rtp_rtcp,
Clock* clock, Clock* clock,
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory, ReceiveStatistics* receive_statistics,
std::unique_ptr<ReceiveStatistics> receive_statistics); rtc::scoped_refptr<AudioDecoderFactory> decoder_factory);
~AudioIngress() override; ~AudioIngress() override;
// Start or stop receiving operation of AudioIngress. // Start or stop receiving operation of AudioIngress.
void StartPlay(); void StartPlay() { playing_ = true; }
void StopPlay(); void StopPlay() {
playing_ = false;
output_audio_level_.ResetLevelFullRange();
}
// Query the state of the AudioIngress. // Query the state of the AudioIngress.
bool Playing() const; bool IsPlaying() const { return playing_; }
// Set the decoder formats and payload type for AcmReceiver where the // Set the decoder formats and payload type for AcmReceiver where the
// key type (int) of the map is the payload type of SdpAudioFormat. // key type (int) of the map is the payload type of SdpAudioFormat.
void SetReceiveCodecs(const std::map<int, SdpAudioFormat>& codecs); void SetReceiveCodecs(const std::map<int, SdpAudioFormat>& codecs);
// APIs to handle received RTP/RTCP packets from caller. // APIs to handle received RTP/RTCP packets from caller.
void ReceivedRTPPacket(const uint8_t* data, size_t length); void ReceivedRTPPacket(rtc::ArrayView<const uint8_t> rtp_packet);
void ReceivedRTCPPacket(const uint8_t* data, size_t length); void ReceivedRTCPPacket(rtc::ArrayView<const uint8_t> rtcp_packet);
// Retrieve highest speech output level in last 100 ms. Note that // Retrieve highest speech output level in last 100 ms. Note that
// this isn't RMS but absolute raw audio level on int16_t sample unit. // this isn't RMS but absolute raw audio level on int16_t sample unit.
// Therefore, the return value will vary between 0 ~ 0xFFFF. This type of // Therefore, the return value will vary between 0 ~ 0xFFFF. This type of
// value may be useful to be used for measuring active speaker gauge. // value may be useful to be used for measuring active speaker gauge.
int GetSpeechOutputLevelFullRange() const; int GetSpeechOutputLevelFullRange() const {
return output_audio_level_.LevelFullRange();
}
// Returns network round trip time (RTT) measued by RTCP exchange with // Returns network round trip time (RTT) measued by RTCP exchange with
// remote media endpoint. RTT value -1 indicates that it's not initialized. // remote media endpoint. RTT value -1 indicates that it's not initialized.
int64_t GetRoundTripTime(); int64_t GetRoundTripTime();
NetworkStatistics GetNetworkStatistics() const; NetworkStatistics GetNetworkStatistics() const {
AudioDecodingCallStats GetDecodingStatistics() const; NetworkStatistics stats;
acm_receiver_.GetNetworkStatistics(&stats);
return stats;
}
AudioDecodingCallStats GetDecodingStatistics() const {
AudioDecodingCallStats stats;
acm_receiver_.GetDecodingCallStatistics(&stats);
return stats;
}
// Implementation of AudioMixer::Source interface. // Implementation of AudioMixer::Source interface.
AudioMixer::Source::AudioFrameInfo GetAudioFrameWithInfo( AudioMixer::Source::AudioFrameInfo GetAudioFrameWithInfo(
int sampling_rate, int sampling_rate,
AudioFrame* audio_frame) override; AudioFrame* audio_frame) override;
int Ssrc() const override; int Ssrc() const override {
int PreferredSampleRate() const override; return rtc::dchecked_cast<int>(remote_ssrc_.load());
}
int PreferredSampleRate() const override {
// If we haven't received any RTP packet from remote and thus
// last_packet_sampling_rate is not available then use NetEq's sampling
// rate as that would be what would be used for audio output sample.
return std::max(acm_receiver_.last_packet_sample_rate_hz().value_or(0),
acm_receiver_.last_output_sample_rate_hz());
}
private: private:
// Indicate AudioIngress status as caller invokes Start/StopPlaying. // Indicates AudioIngress status as caller invokes Start/StopPlaying.
// If not playing, incoming RTP data processing is skipped, thus // If not playing, incoming RTP data processing is skipped, thus
// producing no data to output device. // producing no data to output device.
std::atomic<bool> playing_; std::atomic<bool> playing_;
@ -98,7 +120,7 @@ class AudioIngress : public AudioMixer::Source {
std::atomic<int64_t> first_rtp_timestamp_; std::atomic<int64_t> first_rtp_timestamp_;
// Synchronizaton is handled internally by ReceiveStatistics. // Synchronizaton is handled internally by ReceiveStatistics.
const std::unique_ptr<ReceiveStatistics> rtp_receive_statistics_; ReceiveStatistics* const rtp_receive_statistics_;
// Synchronizaton is handled internally by RtpRtcp. // Synchronizaton is handled internally by RtpRtcp.
RtpRtcp* const rtp_rtcp_; RtpRtcp* const rtp_rtcp_;

View File

@ -9,6 +9,42 @@
import("../../../webrtc.gni") import("../../../webrtc.gni")
if (rtc_include_tests) { if (rtc_include_tests) {
rtc_library("voip_core_unittests") {
testonly = true
sources = [ "voip_core_unittest.cc" ]
deps = [
"..:voip_core",
"../../../api/audio_codecs:builtin_audio_decoder_factory",
"../../../api/audio_codecs:builtin_audio_encoder_factory",
"../../../api/task_queue:default_task_queue_factory",
"../../../modules/audio_device:mock_audio_device",
"../../../modules/audio_processing:mocks",
"../../../test:audio_codec_mocks",
"../../../test:mock_transport",
"../../../test:test_support",
]
}
rtc_library("audio_channel_unittests") {
testonly = true
sources = [ "audio_channel_unittest.cc" ]
deps = [
"..:audio_channel",
"../../../api:transport_api",
"../../../api/audio_codecs:builtin_audio_decoder_factory",
"../../../api/audio_codecs:builtin_audio_encoder_factory",
"../../../api/task_queue:default_task_queue_factory",
"../../../modules/audio_mixer:audio_mixer_impl",
"../../../modules/audio_mixer:audio_mixer_test_utils",
"../../../modules/rtp_rtcp:rtp_rtcp_format",
"../../../modules/utility",
"../../../rtc_base:logging",
"../../../rtc_base:rtc_event",
"../../../test:mock_transport",
"../../../test:test_support",
]
}
rtc_library("audio_ingress_unittests") { rtc_library("audio_ingress_unittests") {
testonly = true testonly = true
sources = [ "audio_ingress_unittest.cc" ] sources = [ "audio_ingress_unittest.cc" ]

View File

@ -0,0 +1,143 @@
/*
* Copyright (c) 2020 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 "audio/voip/audio_channel.h"
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "api/call/transport.h"
#include "api/task_queue/default_task_queue_factory.h"
#include "modules/audio_mixer/audio_mixer_impl.h"
#include "modules/audio_mixer/sine_wave_generator.h"
#include "modules/rtp_rtcp/source/rtp_packet_received.h"
#include "modules/utility/include/process_thread.h"
#include "rtc_base/event.h"
#include "rtc_base/logging.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/mock_transport.h"
namespace webrtc {
namespace {
using ::testing::Invoke;
using ::testing::NiceMock;
using ::testing::Unused;
constexpr uint64_t kStartTime = 123456789;
constexpr uint32_t kLocalSsrc = 0xdeadc0de;
constexpr int16_t kAudioLevel = 3004; // used for sine wave level
constexpr int kPcmuPayload = 0;
class AudioChannelTest : public ::testing::Test {
public:
const SdpAudioFormat kPcmuFormat = {"pcmu", 8000, 1};
AudioChannelTest()
: fake_clock_(kStartTime), wave_generator_(1000.0, kAudioLevel) {
process_thread_ = ProcessThread::Create("ModuleProcessThread");
audio_mixer_ = AudioMixerImpl::Create();
task_queue_factory_ = CreateDefaultTaskQueueFactory();
encoder_factory_ = CreateBuiltinAudioEncoderFactory();
decoder_factory_ = CreateBuiltinAudioDecoderFactory();
}
void SetUp() override {
audio_channel_ = new rtc::RefCountedObject<AudioChannel>(
&transport_, kLocalSsrc, task_queue_factory_.get(),
process_thread_.get(), audio_mixer_.get(), decoder_factory_);
audio_channel_->SetEncoder(kPcmuPayload, kPcmuFormat,
encoder_factory_->MakeAudioEncoder(
kPcmuPayload, kPcmuFormat, absl::nullopt));
audio_channel_->SetReceiveCodecs({{kPcmuPayload, kPcmuFormat}});
audio_channel_->StartSend();
audio_channel_->StartPlay();
}
void TearDown() override {
audio_channel_->StopSend();
audio_channel_->StopPlay();
audio_channel_ = nullptr;
}
std::unique_ptr<AudioFrame> GetAudioFrame(int order) {
auto frame = std::make_unique<AudioFrame>();
frame->sample_rate_hz_ = kPcmuFormat.clockrate_hz;
frame->samples_per_channel_ = kPcmuFormat.clockrate_hz / 100; // 10 ms.
frame->num_channels_ = kPcmuFormat.num_channels;
frame->timestamp_ = frame->samples_per_channel_ * order;
wave_generator_.GenerateNextFrame(frame.get());
return frame;
}
SimulatedClock fake_clock_;
SineWaveGenerator wave_generator_;
NiceMock<MockTransport> transport_;
std::unique_ptr<TaskQueueFactory> task_queue_factory_;
rtc::scoped_refptr<AudioMixer> audio_mixer_;
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory_;
rtc::scoped_refptr<AudioEncoderFactory> encoder_factory_;
std::unique_ptr<ProcessThread> process_thread_;
rtc::scoped_refptr<AudioChannel> audio_channel_;
};
// Validate RTP packet generation by feeding audio frames with sine wave.
// Resulted RTP packet is looped back into AudioChannel and gets decoded into
// audio frame to see if it has some signal to indicate its validity.
TEST_F(AudioChannelTest, PlayRtpByLocalLoop) {
rtc::Event event;
auto loop_rtp = [&](const uint8_t* packet, size_t length, Unused) {
audio_channel_->ReceivedRTPPacket(
rtc::ArrayView<const uint8_t>(packet, length));
event.Set();
return true;
};
EXPECT_CALL(transport_, SendRtp).WillOnce(Invoke(loop_rtp));
auto audio_sender = audio_channel_->GetAudioSender();
audio_sender->SendAudioData(GetAudioFrame(0));
audio_sender->SendAudioData(GetAudioFrame(1));
event.Wait(/*ms=*/1000);
AudioFrame empty_frame, audio_frame;
empty_frame.Mute();
empty_frame.mutable_data(); // This will zero out the data.
audio_frame.CopyFrom(empty_frame);
audio_mixer_->Mix(/*number_of_channels*/ 1, &audio_frame);
// We expect now audio frame to pick up something.
EXPECT_NE(memcmp(empty_frame.data(), audio_frame.data(),
AudioFrame::kMaxDataSizeBytes),
0);
}
// Validate assigned local SSRC is resulted in RTP packet.
TEST_F(AudioChannelTest, VerifyLocalSsrcAsAssigned) {
RtpPacketReceived rtp;
rtc::Event event;
auto loop_rtp = [&](const uint8_t* packet, size_t length, Unused) {
rtp.Parse(packet, length);
event.Set();
return true;
};
EXPECT_CALL(transport_, SendRtp).WillOnce(Invoke(loop_rtp));
auto audio_sender = audio_channel_->GetAudioSender();
audio_sender->SendAudioData(GetAudioFrame(0));
audio_sender->SendAudioData(GetAudioFrame(1));
event.Wait(/*ms=*/1000);
EXPECT_EQ(rtp.Ssrc(), kLocalSsrc);
}
} // namespace
} // namespace webrtc

View File

@ -76,6 +76,7 @@ class AudioEgressTest : public ::testing::Test {
// Make sure we have shut down rtp stack and reset egress for each test. // Make sure we have shut down rtp stack and reset egress for each test.
void TearDown() override { void TearDown() override {
egress_->StopSend();
rtp_rtcp_->SetSendingStatus(false); rtp_rtcp_->SetSendingStatus(false);
egress_.reset(); egress_.reset();
} }
@ -99,10 +100,10 @@ class AudioEgressTest : public ::testing::Test {
SimulatedClock fake_clock_; SimulatedClock fake_clock_;
NiceMock<MockTransport> transport_; NiceMock<MockTransport> transport_;
SineWaveGenerator wave_generator_; SineWaveGenerator wave_generator_;
std::unique_ptr<AudioEgress> egress_;
std::unique_ptr<TaskQueueFactory> task_queue_factory_;
std::unique_ptr<RtpRtcp> rtp_rtcp_; std::unique_ptr<RtpRtcp> rtp_rtcp_;
std::unique_ptr<TaskQueueFactory> task_queue_factory_;
rtc::scoped_refptr<AudioEncoderFactory> encoder_factory_; rtc::scoped_refptr<AudioEncoderFactory> encoder_factory_;
std::unique_ptr<AudioEgress> egress_;
}; };
TEST_F(AudioEgressTest, SendingStatusAfterStartAndStop) { TEST_F(AudioEgressTest, SendingStatusAfterStartAndStop) {

View File

@ -30,26 +30,26 @@ using ::testing::Unused;
constexpr int16_t kAudioLevel = 3004; // Used for sine wave level. constexpr int16_t kAudioLevel = 3004; // Used for sine wave level.
std::unique_ptr<RtpRtcp> CreateRtpStack(Clock* clock, Transport* transport) {
RtpRtcp::Configuration rtp_config;
rtp_config.clock = clock;
rtp_config.audio = true;
rtp_config.rtcp_report_interval_ms = 5000;
rtp_config.outgoing_transport = transport;
rtp_config.local_media_ssrc = 0xdeadc0de;
auto rtp_rtcp = RtpRtcp::Create(rtp_config);
rtp_rtcp->SetSendingMediaStatus(false);
rtp_rtcp->SetRTCPStatus(RtcpMode::kCompound);
return rtp_rtcp;
}
class AudioIngressTest : public ::testing::Test { class AudioIngressTest : public ::testing::Test {
public: public:
const SdpAudioFormat kPcmuFormat = {"pcmu", 8000, 1}; const SdpAudioFormat kPcmuFormat = {"pcmu", 8000, 1};
AudioIngressTest() AudioIngressTest()
: fake_clock_(123456789), wave_generator_(1000.0, kAudioLevel) { : fake_clock_(123456789), wave_generator_(1000.0, kAudioLevel) {
rtp_rtcp_ = CreateRtpStack(&fake_clock_, &transport_); receive_statistics_ = ReceiveStatistics::Create(&fake_clock_);
RtpRtcp::Configuration rtp_config;
rtp_config.clock = &fake_clock_;
rtp_config.audio = true;
rtp_config.receive_statistics = receive_statistics_.get();
rtp_config.rtcp_report_interval_ms = 5000;
rtp_config.outgoing_transport = &transport_;
rtp_config.local_media_ssrc = 0xdeadc0de;
rtp_rtcp_ = RtpRtcp::Create(rtp_config);
rtp_rtcp_->SetSendingMediaStatus(false);
rtp_rtcp_->SetRTCPStatus(RtcpMode::kCompound);
task_queue_factory_ = CreateDefaultTaskQueueFactory(); task_queue_factory_ = CreateDefaultTaskQueueFactory();
encoder_factory_ = CreateBuiltinAudioEncoderFactory(); encoder_factory_ = CreateBuiltinAudioEncoderFactory();
decoder_factory_ = CreateBuiltinAudioDecoderFactory(); decoder_factory_ = CreateBuiltinAudioDecoderFactory();
@ -57,9 +57,9 @@ class AudioIngressTest : public ::testing::Test {
void SetUp() override { void SetUp() override {
constexpr int kPcmuPayload = 0; constexpr int kPcmuPayload = 0;
ingress_ = std::make_unique<AudioIngress>( ingress_ = std::make_unique<AudioIngress>(rtp_rtcp_.get(), &fake_clock_,
rtp_rtcp_.get(), &fake_clock_, decoder_factory_, receive_statistics_.get(),
ReceiveStatistics::Create(&fake_clock_)); decoder_factory_);
ingress_->SetReceiveCodecs({{kPcmuPayload, kPcmuFormat}}); ingress_->SetReceiveCodecs({{kPcmuPayload, kPcmuFormat}});
egress_ = std::make_unique<AudioEgress>(rtp_rtcp_.get(), &fake_clock_, egress_ = std::make_unique<AudioEgress>(rtp_rtcp_.get(), &fake_clock_,
@ -76,6 +76,8 @@ class AudioIngressTest : public ::testing::Test {
rtp_rtcp_->SetSendingStatus(false); rtp_rtcp_->SetSendingStatus(false);
ingress_->StopPlay(); ingress_->StopPlay();
egress_->StopSend(); egress_->StopSend();
egress_.reset();
ingress_.reset();
} }
std::unique_ptr<AudioFrame> GetAudioFrame(int order) { std::unique_ptr<AudioFrame> GetAudioFrame(int order) {
@ -91,25 +93,25 @@ class AudioIngressTest : public ::testing::Test {
SimulatedClock fake_clock_; SimulatedClock fake_clock_;
SineWaveGenerator wave_generator_; SineWaveGenerator wave_generator_;
NiceMock<MockTransport> transport_; NiceMock<MockTransport> transport_;
std::unique_ptr<AudioIngress> ingress_; std::unique_ptr<ReceiveStatistics> receive_statistics_;
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory_; std::unique_ptr<RtpRtcp> rtp_rtcp_;
// Members used to drive the input to ingress.
std::unique_ptr<AudioEgress> egress_;
std::unique_ptr<TaskQueueFactory> task_queue_factory_;
std::shared_ptr<RtpRtcp> rtp_rtcp_;
rtc::scoped_refptr<AudioEncoderFactory> encoder_factory_; rtc::scoped_refptr<AudioEncoderFactory> encoder_factory_;
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory_;
std::unique_ptr<TaskQueueFactory> task_queue_factory_;
std::unique_ptr<AudioIngress> ingress_;
std::unique_ptr<AudioEgress> egress_;
}; };
TEST_F(AudioIngressTest, PlayingAfterStartAndStop) { TEST_F(AudioIngressTest, PlayingAfterStartAndStop) {
EXPECT_EQ(ingress_->Playing(), true); EXPECT_EQ(ingress_->IsPlaying(), true);
ingress_->StopPlay(); ingress_->StopPlay();
EXPECT_EQ(ingress_->Playing(), false); EXPECT_EQ(ingress_->IsPlaying(), false);
} }
TEST_F(AudioIngressTest, GetAudioFrameAfterRtpReceived) { TEST_F(AudioIngressTest, GetAudioFrameAfterRtpReceived) {
rtc::Event event; rtc::Event event;
auto handle_rtp = [&](const uint8_t* packet, size_t length, Unused) { auto handle_rtp = [&](const uint8_t* packet, size_t length, Unused) {
ingress_->ReceivedRTPPacket(packet, length); ingress_->ReceivedRTPPacket(rtc::ArrayView<const uint8_t>(packet, length));
event.Set(); event.Set();
return true; return true;
}; };
@ -137,7 +139,7 @@ TEST_F(AudioIngressTest, GetSpeechOutputLevelFullRange) {
int rtp_count = 0; int rtp_count = 0;
rtc::Event event; rtc::Event event;
auto handle_rtp = [&](const uint8_t* packet, size_t length, Unused) { auto handle_rtp = [&](const uint8_t* packet, size_t length, Unused) {
ingress_->ReceivedRTPPacket(packet, length); ingress_->ReceivedRTPPacket(rtc::ArrayView<const uint8_t>(packet, length));
if (++rtp_count == kNumRtp) { if (++rtp_count == kNumRtp) {
event.Set(); event.Set();
} }
@ -162,7 +164,7 @@ TEST_F(AudioIngressTest, GetSpeechOutputLevelFullRange) {
TEST_F(AudioIngressTest, PreferredSampleRate) { TEST_F(AudioIngressTest, PreferredSampleRate) {
rtc::Event event; rtc::Event event;
auto handle_rtp = [&](const uint8_t* packet, size_t length, Unused) { auto handle_rtp = [&](const uint8_t* packet, size_t length, Unused) {
ingress_->ReceivedRTPPacket(packet, length); ingress_->ReceivedRTPPacket(rtc::ArrayView<const uint8_t>(packet, length));
event.Set(); event.Set();
return true; return true;
}; };

View File

@ -0,0 +1,100 @@
/*
* Copyright (c) 2020 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 "audio/voip/voip_core.h"
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "api/task_queue/default_task_queue_factory.h"
#include "modules/audio_device/include/mock_audio_device.h"
#include "modules/audio_processing/include/mock_audio_processing.h"
#include "test/gtest.h"
#include "test/mock_transport.h"
namespace webrtc {
namespace {
using ::testing::NiceMock;
using ::testing::Return;
constexpr int kPcmuPayload = 0;
class VoipCoreTest : public ::testing::Test {
public:
const SdpAudioFormat kPcmuFormat = {"pcmu", 8000, 1};
VoipCoreTest() { audio_device_ = test::MockAudioDeviceModule::CreateNice(); }
void SetUp() override {
auto encoder_factory = CreateBuiltinAudioEncoderFactory();
auto decoder_factory = CreateBuiltinAudioDecoderFactory();
rtc::scoped_refptr<AudioProcessing> audio_processing =
new rtc::RefCountedObject<test::MockAudioProcessing>();
voip_core_ = std::make_unique<VoipCore>();
voip_core_->Init(std::move(encoder_factory), std::move(decoder_factory),
CreateDefaultTaskQueueFactory(), audio_device_,
std::move(audio_processing));
}
std::unique_ptr<VoipCore> voip_core_;
NiceMock<MockTransport> transport_;
rtc::scoped_refptr<test::MockAudioDeviceModule> audio_device_;
};
// Validate expected API calls that involves with VoipCore. Some verification is
// involved with checking mock audio device.
TEST_F(VoipCoreTest, BasicVoipCoreOperation) {
// Program mock as non-operational and ready to start.
EXPECT_CALL(*audio_device_, Recording()).WillOnce(Return(false));
EXPECT_CALL(*audio_device_, Playing()).WillOnce(Return(false));
EXPECT_CALL(*audio_device_, InitRecording()).WillOnce(Return(0));
EXPECT_CALL(*audio_device_, InitPlayout()).WillOnce(Return(0));
EXPECT_CALL(*audio_device_, StartRecording()).WillOnce(Return(0));
EXPECT_CALL(*audio_device_, StartPlayout()).WillOnce(Return(0));
auto channel = voip_core_->CreateChannel(&transport_, 0xdeadc0de);
EXPECT_TRUE(channel);
voip_core_->SetSendCodec(*channel, kPcmuPayload, kPcmuFormat);
voip_core_->SetReceiveCodecs(*channel, {{kPcmuPayload, kPcmuFormat}});
EXPECT_TRUE(voip_core_->StartSend(*channel));
EXPECT_TRUE(voip_core_->StartPlayout(*channel));
// Program mock as operational that is ready to be stopped.
EXPECT_CALL(*audio_device_, Recording()).WillOnce(Return(true));
EXPECT_CALL(*audio_device_, Playing()).WillOnce(Return(true));
EXPECT_CALL(*audio_device_, StopRecording()).WillOnce(Return(0));
EXPECT_CALL(*audio_device_, StopPlayout()).WillOnce(Return(0));
EXPECT_TRUE(voip_core_->StopSend(*channel));
EXPECT_TRUE(voip_core_->StopPlayout(*channel));
voip_core_->ReleaseChannel(*channel);
}
TEST_F(VoipCoreTest, ExpectFailToUseReleasedChannelId) {
auto channel = voip_core_->CreateChannel(&transport_, 0xdeadc0de);
EXPECT_TRUE(channel);
// Release right after creation.
voip_core_->ReleaseChannel(*channel);
// Now use released channel.
// These should be no-op.
voip_core_->SetSendCodec(*channel, kPcmuPayload, kPcmuFormat);
voip_core_->SetReceiveCodecs(*channel, {{kPcmuPayload, kPcmuFormat}});
EXPECT_FALSE(voip_core_->StartSend(*channel));
EXPECT_FALSE(voip_core_->StartPlayout(*channel));
}
} // namespace
} // namespace webrtc

348
audio/voip/voip_core.cc Normal file
View File

@ -0,0 +1,348 @@
/*
* Copyright (c) 2020 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 "audio/voip/voip_core.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "api/audio_codecs/audio_format.h"
#include "rtc_base/critical_section.h"
#include "rtc_base/logging.h"
namespace webrtc {
namespace {
// For Windows, use specific enum type to initialize default audio device as
// defined in AudioDeviceModule::WindowsDeviceType.
#if defined(WEBRTC_WIN)
constexpr AudioDeviceModule::WindowsDeviceType kAudioDeviceId =
AudioDeviceModule::WindowsDeviceType::kDefaultCommunicationDevice;
#else
constexpr uint16_t kAudioDeviceId = 0;
#endif // defined(WEBRTC_WIN)
// Maximum value range limit on ChannelId. This can be increased without any
// side effect and only set at this moderate value for better readability for
// logging.
static constexpr int kMaxChannelId = 100000;
} // namespace
bool VoipCore::Init(rtc::scoped_refptr<AudioEncoderFactory> encoder_factory,
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory,
std::unique_ptr<TaskQueueFactory> task_queue_factory,
rtc::scoped_refptr<AudioDeviceModule> audio_device_module,
rtc::scoped_refptr<AudioProcessing> audio_processing) {
encoder_factory_ = std::move(encoder_factory);
decoder_factory_ = std::move(decoder_factory);
task_queue_factory_ = std::move(task_queue_factory);
audio_device_module_ = std::move(audio_device_module);
process_thread_ = ProcessThread::Create("ModuleProcessThread");
audio_mixer_ = AudioMixerImpl::Create();
if (audio_processing) {
audio_processing_ = std::move(audio_processing);
AudioProcessing::Config apm_config = audio_processing_->GetConfig();
apm_config.echo_canceller.enabled = true;
audio_processing_->ApplyConfig(apm_config);
}
// AudioTransportImpl depends on audio mixer and audio processing instances.
audio_transport_ = std::make_unique<AudioTransportImpl>(
audio_mixer_.get(), audio_processing_.get());
// Initialize ADM.
if (audio_device_module_->Init() != 0) {
RTC_LOG(LS_ERROR) << "Failed to initialize the ADM.";
return false;
}
// Note that failures on initializing default recording/speaker devices are
// not considered to be fatal here. In certain case, caller may not care about
// recording device functioning (e.g webinar where only speaker is available).
// It's also possible that there are other audio devices available that may
// work.
// TODO(natim@webrtc.org): consider moving this part out of initialization.
// Initialize default speaker device.
if (audio_device_module_->SetPlayoutDevice(kAudioDeviceId) != 0) {
RTC_LOG(LS_WARNING) << "Unable to set playout device.";
}
if (audio_device_module_->InitSpeaker() != 0) {
RTC_LOG(LS_WARNING) << "Unable to access speaker.";
}
// Initialize default recording device.
if (audio_device_module_->SetRecordingDevice(kAudioDeviceId) != 0) {
RTC_LOG(LS_WARNING) << "Unable to set recording device.";
}
if (audio_device_module_->InitMicrophone() != 0) {
RTC_LOG(LS_WARNING) << "Unable to access microphone.";
}
// Set number of channels on speaker device.
bool available = false;
if (audio_device_module_->StereoPlayoutIsAvailable(&available) != 0) {
RTC_LOG(LS_WARNING) << "Unable to query stereo playout.";
}
if (audio_device_module_->SetStereoPlayout(available) != 0) {
RTC_LOG(LS_WARNING) << "Unable to set mono/stereo playout mode.";
}
// Set number of channels on recording device.
available = false;
if (audio_device_module_->StereoRecordingIsAvailable(&available) != 0) {
RTC_LOG(LS_WARNING) << "Unable to query stereo recording.";
}
if (audio_device_module_->SetStereoRecording(available) != 0) {
RTC_LOG(LS_WARNING) << "Unable to set stereo recording mode.";
}
if (audio_device_module_->RegisterAudioCallback(audio_transport_.get()) !=
0) {
RTC_LOG(LS_WARNING) << "Unable to register audio callback.";
}
return true;
}
absl::optional<ChannelId> VoipCore::CreateChannel(
Transport* transport,
absl::optional<uint32_t> local_ssrc) {
absl::optional<ChannelId> channel;
// Set local ssrc to random if not set by caller.
if (!local_ssrc) {
Random random(rtc::TimeMicros());
local_ssrc = random.Rand<uint32_t>();
}
rtc::scoped_refptr<AudioChannel> audio_channel =
new rtc::RefCountedObject<AudioChannel>(
transport, local_ssrc.value(), task_queue_factory_.get(),
process_thread_.get(), audio_mixer_.get(), decoder_factory_);
{
rtc::CritScope lock(&lock_);
channel = static_cast<ChannelId>(next_channel_id_);
channels_[*channel] = audio_channel;
next_channel_id_++;
if (next_channel_id_ >= kMaxChannelId) {
next_channel_id_ = 0;
}
}
// Set ChannelId in audio channel for logging/debugging purpose.
audio_channel->SetId(*channel);
return channel;
}
void VoipCore::ReleaseChannel(ChannelId channel) {
// Destroy channel outside of the lock.
rtc::scoped_refptr<AudioChannel> audio_channel;
{
rtc::CritScope lock(&lock_);
auto iter = channels_.find(channel);
if (iter != channels_.end()) {
audio_channel = std::move(iter->second);
channels_.erase(iter);
}
}
if (!audio_channel) {
RTC_LOG(LS_WARNING) << "Channel " << channel << " not found";
}
}
rtc::scoped_refptr<AudioChannel> VoipCore::GetChannel(ChannelId channel) {
rtc::scoped_refptr<AudioChannel> audio_channel;
{
rtc::CritScope lock(&lock_);
auto iter = channels_.find(channel);
if (iter != channels_.end()) {
audio_channel = iter->second;
}
}
if (!audio_channel) {
RTC_LOG(LS_ERROR) << "Channel " << channel << " not found";
}
return audio_channel;
}
bool VoipCore::UpdateAudioTransportWithSenders() {
std::vector<AudioSender*> audio_senders;
// Gather a list of audio channel that are currently sending along with
// highest sampling rate and channel numbers to configure into audio
// transport.
int max_sampling_rate = 8000;
size_t max_num_channels = 1;
{
rtc::CritScope lock(&lock_);
// Reserve to prevent run time vector re-allocation.
audio_senders.reserve(channels_.size());
for (auto kv : channels_) {
rtc::scoped_refptr<AudioChannel>& channel = kv.second;
if (channel->IsSendingMedia()) {
auto encoder_format = channel->GetEncoderFormat();
if (!encoder_format) {
RTC_LOG(LS_ERROR)
<< "channel " << channel->GetId() << " encoder is not set";
continue;
}
audio_senders.push_back(channel->GetAudioSender());
max_sampling_rate =
std::max(max_sampling_rate, encoder_format->clockrate_hz);
max_num_channels =
std::max(max_num_channels, encoder_format->num_channels);
}
}
}
audio_transport_->UpdateAudioSenders(audio_senders, max_sampling_rate,
max_num_channels);
// Depending on availability of senders, turn on or off ADM recording.
if (!audio_senders.empty()) {
if (!audio_device_module_->Recording()) {
if (audio_device_module_->InitRecording() != 0) {
RTC_LOG(LS_ERROR) << "InitRecording failed";
return false;
}
if (audio_device_module_->StartRecording() != 0) {
RTC_LOG(LS_ERROR) << "StartRecording failed";
return false;
}
}
} else {
if (audio_device_module_->Recording() &&
audio_device_module_->StopRecording() != 0) {
RTC_LOG(LS_ERROR) << "StopRecording failed";
return false;
}
}
return true;
}
bool VoipCore::StartSend(ChannelId channel) {
auto audio_channel = GetChannel(channel);
if (!audio_channel) {
return false;
}
audio_channel->StartSend();
return UpdateAudioTransportWithSenders();
}
bool VoipCore::StopSend(ChannelId channel) {
auto audio_channel = GetChannel(channel);
if (!audio_channel) {
return false;
}
audio_channel->StopSend();
return UpdateAudioTransportWithSenders();
}
bool VoipCore::StartPlayout(ChannelId channel) {
auto audio_channel = GetChannel(channel);
if (!audio_channel) {
return false;
}
audio_channel->StartPlay();
if (!audio_device_module_->Playing()) {
if (audio_device_module_->InitPlayout() != 0) {
RTC_LOG(LS_ERROR) << "InitPlayout failed";
return false;
}
if (audio_device_module_->StartPlayout() != 0) {
RTC_LOG(LS_ERROR) << "StartPlayout failed";
return false;
}
}
return true;
}
bool VoipCore::StopPlayout(ChannelId channel) {
auto audio_channel = GetChannel(channel);
if (!audio_channel) {
return false;
}
audio_channel->StopPlay();
bool stop_device = true;
{
rtc::CritScope lock(&lock_);
for (auto kv : channels_) {
rtc::scoped_refptr<AudioChannel>& channel = kv.second;
if (channel->IsPlaying()) {
stop_device = false;
break;
}
}
}
if (stop_device && audio_device_module_->Playing()) {
if (audio_device_module_->StopPlayout() != 0) {
RTC_LOG(LS_ERROR) << "StopPlayout failed";
return false;
}
}
return true;
}
void VoipCore::ReceivedRTPPacket(ChannelId channel,
rtc::ArrayView<const uint8_t> rtp_packet) {
// Failure to locate channel is logged internally in GetChannel.
if (auto audio_channel = GetChannel(channel)) {
audio_channel->ReceivedRTPPacket(rtp_packet);
}
}
void VoipCore::ReceivedRTCPPacket(ChannelId channel,
rtc::ArrayView<const uint8_t> rtcp_packet) {
// Failure to locate channel is logged internally in GetChannel.
if (auto audio_channel = GetChannel(channel)) {
audio_channel->ReceivedRTCPPacket(rtcp_packet);
}
}
void VoipCore::SetSendCodec(ChannelId channel,
int payload_type,
const SdpAudioFormat& encoder_format) {
// Failure to locate channel is logged internally in GetChannel.
if (auto audio_channel = GetChannel(channel)) {
auto encoder = encoder_factory_->MakeAudioEncoder(
payload_type, encoder_format, absl::nullopt);
audio_channel->SetEncoder(payload_type, encoder_format, std::move(encoder));
}
}
void VoipCore::SetReceiveCodecs(
ChannelId channel,
const std::map<int, SdpAudioFormat>& decoder_specs) {
// Failure to locate channel is logged internally in GetChannel.
if (auto audio_channel = GetChannel(channel)) {
audio_channel->SetReceiveCodecs(decoder_specs);
}
}
} // namespace webrtc

139
audio/voip/voip_core.h Normal file
View File

@ -0,0 +1,139 @@
/*
* Copyright (c) 2020 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 AUDIO_VOIP_VOIP_CORE_H_
#define AUDIO_VOIP_VOIP_CORE_H_
#include <map>
#include <memory>
#include <queue>
#include <unordered_map>
#include <vector>
#include "api/audio_codecs/audio_decoder_factory.h"
#include "api/audio_codecs/audio_encoder_factory.h"
#include "api/scoped_refptr.h"
#include "api/task_queue/task_queue_factory.h"
#include "api/voip/voip_base.h"
#include "api/voip/voip_codec.h"
#include "api/voip/voip_engine.h"
#include "api/voip/voip_network.h"
#include "audio/audio_transport_impl.h"
#include "audio/voip/audio_channel.h"
#include "modules/audio_device/include/audio_device.h"
#include "modules/audio_mixer/audio_mixer_impl.h"
#include "modules/audio_processing/include/audio_processing.h"
#include "modules/utility/include/process_thread.h"
#include "rtc_base/critical_section.h"
namespace webrtc {
// VoipCore is the implementatino of VoIP APIs listed in api/voip directory.
// It manages a vector of AudioChannel objects where each is mapped with a
// ChannelId (int) type. ChannelId is the primary key to locate a specific
// AudioChannel object to operate requested VoIP API from the caller.
//
// This class receives required audio components from caller at construction and
// owns the life cycle of them to orchestrate the proper destruction sequence.
class VoipCore : public VoipEngine,
public VoipBase,
public VoipNetwork,
public VoipCodec {
public:
~VoipCore() override = default;
// Initialize VoipCore components with provided arguments.
// Returns false only when |audio_device_module| fails to initialize which
// would presumably render further processing useless.
// TODO(natim@webrtc.org): Need to report audio device errors to user layer.
bool Init(rtc::scoped_refptr<AudioEncoderFactory> encoder_factory,
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory,
std::unique_ptr<TaskQueueFactory> task_queue_factory,
rtc::scoped_refptr<AudioDeviceModule> audio_device_module,
rtc::scoped_refptr<AudioProcessing> audio_processing);
// Implements VoipEngine interfaces.
VoipBase& Base() override { return *this; }
VoipNetwork& Network() override { return *this; }
VoipCodec& Codec() override { return *this; }
// Implements VoipBase interfaces.
absl::optional<ChannelId> CreateChannel(
Transport* transport,
absl::optional<uint32_t> local_ssrc) override;
void ReleaseChannel(ChannelId channel) override;
bool StartSend(ChannelId channel) override;
bool StopSend(ChannelId channel) override;
bool StartPlayout(ChannelId channel) override;
bool StopPlayout(ChannelId channel) override;
// Implements VoipNetwork interfaces.
void ReceivedRTPPacket(ChannelId channel,
rtc::ArrayView<const uint8_t> rtp_packet) override;
void ReceivedRTCPPacket(ChannelId channel,
rtc::ArrayView<const uint8_t> rtcp_packet) override;
// Implements VoipCodec interfaces.
void SetSendCodec(ChannelId channel,
int payload_type,
const SdpAudioFormat& encoder_format) override;
void SetReceiveCodecs(
ChannelId channel,
const std::map<int, SdpAudioFormat>& decoder_specs) override;
private:
// Fetches the corresponding AudioChannel assigned with given |channel|.
// Returns nullptr if not found.
rtc::scoped_refptr<AudioChannel> GetChannel(ChannelId channel);
// Updates AudioTransportImpl with a new set of actively sending AudioSender
// (AudioEgress). This needs to be invoked whenever StartSend/StopSend is
// involved by caller. Returns false when the selected audio device fails to
// initialize where it can't expect to deliver any audio input sample.
bool UpdateAudioTransportWithSenders();
// Synchronization for these are handled internally.
rtc::scoped_refptr<AudioEncoderFactory> encoder_factory_;
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory_;
std::unique_ptr<TaskQueueFactory> task_queue_factory_;
// Synchronization is handled internally by AudioProessing.
// Must be placed before |audio_device_module_| for proper destruction.
rtc::scoped_refptr<AudioProcessing> audio_processing_;
// Synchronization is handled internally by AudioMixer.
// Must be placed before |audio_device_module_| for proper destruction.
rtc::scoped_refptr<AudioMixer> audio_mixer_;
// Synchronization is handled internally by AudioTransportImpl.
// Must be placed before |audio_device_module_| for proper destruction.
std::unique_ptr<AudioTransportImpl> audio_transport_;
// Synchronization is handled internally by AudioDeviceModule.
rtc::scoped_refptr<AudioDeviceModule> audio_device_module_;
// Synchronization is handled internally by ProcessThread.
// Must be placed before |channels_| for proper destruction.
std::unique_ptr<ProcessThread> process_thread_;
rtc::CriticalSection lock_;
// Member to track a next ChannelId for new AudioChannel.
int next_channel_id_ RTC_GUARDED_BY(lock_) = 0;
// Container to track currently active AudioChannel objects mapped by
// ChannelId.
std::unordered_map<ChannelId, rtc::scoped_refptr<AudioChannel>> channels_
RTC_GUARDED_BY(lock_);
};
} // namespace webrtc
#endif // AUDIO_VOIP_VOIP_CORE_H_