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:
parent
c064467b32
commit
c0df5fc25b
3
BUILD.gn
3
BUILD.gn
@ -687,8 +687,11 @@ if (rtc_include_tests) {
|
||||
rtc_test("voip_unittests") {
|
||||
testonly = true
|
||||
deps = [
|
||||
"api/voip:voip_engine_factory_unittests",
|
||||
"audio/voip/test:audio_channel_unittests",
|
||||
"audio/voip/test:audio_egress_unittests",
|
||||
"audio/voip/test:audio_ingress_unittests",
|
||||
"audio/voip/test:voip_core_unittests",
|
||||
"test:test_main",
|
||||
]
|
||||
}
|
||||
|
||||
@ -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
|
||||
#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.
|
||||
# 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.
|
||||
|
||||
import("../../webrtc.gni")
|
||||
|
||||
@ -22,3 +22,36 @@ rtc_source_set("voip_api") {
|
||||
"//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",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,4 +2,9 @@ specific_include_rules = {
|
||||
".*\.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",
|
||||
],
|
||||
}
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
/*
|
||||
* 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_BASE_H_
|
||||
#define API_VOIP_VOIP_BASE_H_
|
||||
|
||||
#include "third_party/absl/types/optional.h"
|
||||
#include "absl/types/optional.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
@ -51,13 +51,11 @@ class VoipBase {
|
||||
Transport* transport,
|
||||
absl::optional<uint32_t> local_ssrc) = 0;
|
||||
|
||||
// Releases |channel_id| that has served the purpose.
|
||||
// Released channel will be re-allocated again that invoking operations
|
||||
// on released |channel_id| will lead to undefined behavior.
|
||||
// Releases |channel_id| that no longer has any use.
|
||||
virtual void ReleaseChannel(ChannelId channel_id) = 0;
|
||||
|
||||
// Starts sending on |channel_id|. This will start microphone if first to
|
||||
// start. Returns false if initialization has failed on selected microphone
|
||||
// Starts sending on |channel_id|. This will start microphone if not started
|
||||
// yet. Returns false if initialization has failed on selected microphone
|
||||
// device. API is subject to expand to reflect error condition to application
|
||||
// later.
|
||||
virtual bool StartSend(ChannelId channel_id) = 0;
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
/*
|
||||
* 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_CODEC_H_
|
||||
#define API_VOIP_VOIP_CODEC_H_
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
/*
|
||||
* 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_H_
|
||||
#define API_VOIP_VOIP_ENGINE_H_
|
||||
@ -17,50 +17,60 @@ class VoipBase;
|
||||
class VoipCodec;
|
||||
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.
|
||||
// Therefore, application must synchronize the usage within the life span of
|
||||
// created VoipEngine instance.
|
||||
// // Caller is responsible of setting desired audio components.
|
||||
// VoipEngineConfig config;
|
||||
// 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 =
|
||||
// webrtc::VoipEngineBuilder()
|
||||
// .SetAudioEncoderFactory(CreateBuiltinAudioEncoderFactory())
|
||||
// .SetAudioDecoderFactory(CreateBuiltinAudioDecoderFactory())
|
||||
// .Create();
|
||||
// auto voip_engine = CreateVoipEngine(std::move(config));
|
||||
// if (!voip_engine) return some_failure;
|
||||
//
|
||||
// auto voip_base = voip_engine->Base();
|
||||
// auto voip_codec = voip_engine->Codec();
|
||||
// auto voip_network = voip_engine->Network();
|
||||
// auto& voip_base = voip_engine->Base();
|
||||
// auto& voip_codec = voip_engine->Codec();
|
||||
// auto& voip_network = voip_engine->Network();
|
||||
//
|
||||
// VoipChannel::Config config = { &app_transport_, 0xdeadc0de };
|
||||
// int channel = voip_base.CreateChannel(config);
|
||||
// absl::optional<ChannelId> channel =
|
||||
// voip_base.CreateChannel(&app_transport_);
|
||||
// if (!channel) return some_failure;
|
||||
//
|
||||
// // After SDP offer/answer, payload type and codec usage have been
|
||||
// // decided through negotiation.
|
||||
// voip_codec.SetSendCodec(channel, ...);
|
||||
// voip_codec.SetReceiveCodecs(channel, ...);
|
||||
// // After SDP offer/answer, set payload type and codecs that have been
|
||||
// // decided through SDP negotiation.
|
||||
// voip_codec.SetSendCodec(*channel, ...);
|
||||
// voip_codec.SetReceiveCodecs(*channel, ...);
|
||||
//
|
||||
// // Start Send/Playout on voip channel.
|
||||
// voip_base.StartSend(channel);
|
||||
// voip_base.StartPlayout(channel);
|
||||
// // Start sending and playing RTP on voip channel.
|
||||
// voip_base.StartSend(*channel);
|
||||
// voip_base.StartPlayout(*channel);
|
||||
//
|
||||
// // Inject received rtp/rtcp thru voip network interface.
|
||||
// voip_network.ReceivedRTPPacket(channel, rtp_data, rtp_size);
|
||||
// voip_network.ReceivedRTCPPacket(channel, rtcp_data, rtcp_size);
|
||||
// // Inject received RTP/RTCP through VoipNetwork interface.
|
||||
// voip_network.ReceivedRTPPacket(*channel, ...);
|
||||
// voip_network.ReceivedRTCPPacket(*channel, ...);
|
||||
//
|
||||
// // Stop and release voip channel.
|
||||
// voip_base.StopSend(channel);
|
||||
// voip_base.StopPlayout(channel);
|
||||
//
|
||||
// voip_base.ReleaseChannel(channel);
|
||||
// voip_base.StopSend(*channel);
|
||||
// voip_base.StopPlayout(*channel);
|
||||
// voip_base.ReleaseChannel(*channel);
|
||||
//
|
||||
// Current VoipEngine defines three sub-API classes and is subject to expand in
|
||||
// near future.
|
||||
class VoipEngine {
|
||||
public:
|
||||
virtual ~VoipEngine() = default;
|
||||
|
||||
// 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;
|
||||
|
||||
// VoipNetwork provides injection APIs that would enable application
|
||||
|
||||
44
api/voip/voip_engine_factory.cc
Normal file
44
api/voip/voip_engine_factory.cc
Normal 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
|
||||
71
api/voip/voip_engine_factory.h
Normal file
71
api/voip/voip_engine_factory.h
Normal 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_
|
||||
51
api/voip/voip_engine_factory_unittest.cc
Normal file
51
api/voip/voip_engine_factory_unittest.cc
Normal 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
|
||||
@ -1,12 +1,12 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
/*
|
||||
* 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_NETWORK_H_
|
||||
#define API_VOIP_VOIP_NETWORK_H_
|
||||
@ -16,24 +16,24 @@
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// VoipNetwork interface currently provides any network related interface
|
||||
// such as processing received RTP/RTCP packet from remote endpoint.
|
||||
// The interface subject to expand as needed.
|
||||
//
|
||||
// This interface requires a channel handle created via VoipBase interface.
|
||||
// VoipNetwork interface provides any network related interfaces such as
|
||||
// processing received RTP/RTCP packet from remote endpoint. This interface
|
||||
// requires a ChannelId created via VoipBase interface. Note that using invalid
|
||||
// (previously released) ChannelId will silently fail these API calls as it
|
||||
// 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 {
|
||||
public:
|
||||
// The packets received from the network should be passed to this
|
||||
// function. Note that the data including the RTP-header must also be
|
||||
// given to the VoipEngine.
|
||||
// The data received from the network including RTP header is passed here.
|
||||
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
|
||||
// function. Note that the data including the RTCP-header must also be
|
||||
// given to the VoipEngine.
|
||||
virtual void ReceivedRTCPPacket(ChannelId channel_id,
|
||||
rtc::ArrayView<const uint8_t> data) = 0;
|
||||
// The data received from the network including RTCP header is passed here.
|
||||
virtual void ReceivedRTCPPacket(
|
||||
ChannelId channel_id,
|
||||
rtc::ArrayView<const uint8_t> rtcp_packet) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~VoipNetwork() = default;
|
||||
|
||||
@ -8,20 +8,64 @@
|
||||
|
||||
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") {
|
||||
sources = [
|
||||
"audio_ingress.cc",
|
||||
"audio_ingress.h",
|
||||
]
|
||||
deps = [
|
||||
"..:audio",
|
||||
"../../api:array_view",
|
||||
"../../api:rtp_headers",
|
||||
"../../api:scoped_refptr",
|
||||
"../../api:transport_api",
|
||||
"../../api/audio:audio_mixer_api",
|
||||
"../../api/audio_codecs:audio_codecs_api",
|
||||
"../../audio",
|
||||
"../../audio/utility:audio_frame_operations",
|
||||
"../../modules/audio_coding",
|
||||
"../../modules/rtp_rtcp",
|
||||
"../../modules/rtp_rtcp:rtp_rtcp_format",
|
||||
@ -30,6 +74,7 @@ rtc_library("audio_ingress") {
|
||||
"../../rtc_base:logging",
|
||||
"../../rtc_base:safe_minmax",
|
||||
"../../rtc_base:timeutils",
|
||||
"../utility:audio_frame_operations",
|
||||
]
|
||||
}
|
||||
|
||||
@ -39,10 +84,9 @@ rtc_library("audio_egress") {
|
||||
"audio_egress.h",
|
||||
]
|
||||
deps = [
|
||||
"..:audio",
|
||||
"../../api/audio_codecs:audio_codecs_api",
|
||||
"../../api/task_queue",
|
||||
"../../audio",
|
||||
"../../audio/utility:audio_frame_operations",
|
||||
"../../call:audio_sender_interface",
|
||||
"../../modules/audio_coding",
|
||||
"../../modules/rtp_rtcp",
|
||||
@ -51,5 +95,6 @@ rtc_library("audio_egress") {
|
||||
"../../rtc_base:rtc_task_queue",
|
||||
"../../rtc_base:thread_checker",
|
||||
"../../rtc_base:timeutils",
|
||||
"../utility:audio_frame_operations",
|
||||
]
|
||||
}
|
||||
|
||||
126
audio/voip/audio_channel.cc
Normal file
126
audio/voip/audio_channel.cc
Normal 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
|
||||
98
audio/voip/audio_channel.h
Normal file
98
audio/voip/audio_channel.h
Normal 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_
|
||||
@ -34,18 +34,16 @@ AudioEgress::~AudioEgress() {
|
||||
}
|
||||
|
||||
bool AudioEgress::IsSending() const {
|
||||
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
|
||||
return rtp_rtcp_->SendingMedia();
|
||||
}
|
||||
|
||||
void AudioEgress::SetEncoder(int payload_type,
|
||||
const SdpAudioFormat& encoder_format,
|
||||
std::unique_ptr<AudioEncoder> encoder) {
|
||||
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
|
||||
RTC_DCHECK_GE(payload_type, 0);
|
||||
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)
|
||||
// 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));
|
||||
}
|
||||
|
||||
absl::optional<SdpAudioFormat> AudioEgress::GetEncoderFormat() const {
|
||||
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
|
||||
return encoder_format_;
|
||||
}
|
||||
|
||||
void AudioEgress::StartSend() {
|
||||
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
|
||||
|
||||
rtp_rtcp_->SetSendingMediaStatus(true);
|
||||
}
|
||||
|
||||
void AudioEgress::StopSend() {
|
||||
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
|
||||
|
||||
rtp_rtcp_->SetSendingMediaStatus(false);
|
||||
}
|
||||
|
||||
@ -144,7 +133,6 @@ int32_t AudioEgress::SendData(AudioFrameType frame_type,
|
||||
|
||||
void AudioEgress::RegisterTelephoneEventType(int rtp_payload_type,
|
||||
int sample_rate_hz) {
|
||||
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
|
||||
RTC_DCHECK_GE(rtp_payload_type, 0);
|
||||
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) {
|
||||
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
|
||||
RTC_DCHECK_GE(dtmf_event, 0);
|
||||
RTC_DCHECK_LE(dtmf_event, 255);
|
||||
RTC_DCHECK_GE(duration_ms, 0);
|
||||
@ -175,8 +162,6 @@ bool AudioEgress::SendTelephoneEvent(int dtmf_event, int duration_ms) {
|
||||
}
|
||||
|
||||
void AudioEgress::SetMute(bool mute) {
|
||||
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
|
||||
|
||||
encoder_queue_.PostTask([this, mute] {
|
||||
RTC_DCHECK_RUN_ON(&encoder_queue_);
|
||||
encoder_context_.mute_ = mute;
|
||||
|
||||
@ -34,10 +34,9 @@ namespace webrtc {
|
||||
// encoded payload will be packetized by the RTP stack, resulting in ready to
|
||||
// send RTP packet to remote endpoint.
|
||||
//
|
||||
// This class enforces single worker thread access by caller via SequenceChecker
|
||||
// in debug mode as expected thread usage pattern. In order to minimize the hold
|
||||
// on audio input thread from OS, TaskQueue is employed to encode and send RTP
|
||||
// asynchrounously.
|
||||
// TaskQueue is used to encode and send RTP asynchrounously as some OS platform
|
||||
// uses the same thread for both audio input and output sample deliveries which
|
||||
// can affect audio quality.
|
||||
//
|
||||
// Note that this class is originally based on ChannelSend in
|
||||
// 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
|
||||
// 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.
|
||||
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;
|
||||
|
||||
private:
|
||||
// Ensure that single worker thread access.
|
||||
SequenceChecker worker_thread_checker_;
|
||||
void SetEncoderFormat(const SdpAudioFormat& encoder_format) {
|
||||
rtc::CritScope lock(&lock_);
|
||||
encoder_format_ = encoder_format;
|
||||
}
|
||||
|
||||
rtc::CriticalSection lock_;
|
||||
|
||||
// Current encoder format selected by caller.
|
||||
absl::optional<SdpAudioFormat> encoder_format_
|
||||
RTC_GUARDED_BY(worker_thread_checker_);
|
||||
absl::optional<SdpAudioFormat> encoder_format_ RTC_GUARDED_BY(lock_);
|
||||
|
||||
// Synchronization is handled internally by RtpRtcp.
|
||||
RtpRtcp* const rtp_rtcp_;
|
||||
|
||||
@ -38,27 +38,18 @@ AudioCodingModule::Config CreateAcmConfig(
|
||||
AudioIngress::AudioIngress(
|
||||
RtpRtcp* rtp_rtcp,
|
||||
Clock* clock,
|
||||
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory,
|
||||
std::unique_ptr<ReceiveStatistics> receive_statistics)
|
||||
ReceiveStatistics* receive_statistics,
|
||||
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory)
|
||||
: playing_(false),
|
||||
remote_ssrc_(0),
|
||||
first_rtp_timestamp_(-1),
|
||||
rtp_receive_statistics_(std::move(receive_statistics)),
|
||||
rtp_receive_statistics_(receive_statistics),
|
||||
rtp_rtcp_(rtp_rtcp),
|
||||
acm_receiver_(CreateAcmConfig(decoder_factory)),
|
||||
ntp_estimator_(clock) {}
|
||||
|
||||
AudioIngress::~AudioIngress() = default;
|
||||
|
||||
void AudioIngress::StartPlay() {
|
||||
playing_ = true;
|
||||
}
|
||||
|
||||
void AudioIngress::StopPlay() {
|
||||
playing_ = false;
|
||||
output_audio_level_.ResetLevelFullRange();
|
||||
}
|
||||
|
||||
AudioMixer::Source::AudioFrameInfo AudioIngress::GetAudioFrameWithInfo(
|
||||
int sampling_rate,
|
||||
AudioFrame* audio_frame) {
|
||||
@ -113,17 +104,6 @@ AudioMixer::Source::AudioFrameInfo AudioIngress::GetAudioFrameWithInfo(
|
||||
: 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(
|
||||
const std::map<int, SdpAudioFormat>& codecs) {
|
||||
{
|
||||
@ -135,36 +115,37 @@ void AudioIngress::SetReceiveCodecs(
|
||||
acm_receiver_.SetCodecs(codecs);
|
||||
}
|
||||
|
||||
void AudioIngress::ReceivedRTPPacket(const uint8_t* data, size_t length) {
|
||||
if (!Playing()) {
|
||||
void AudioIngress::ReceivedRTPPacket(rtc::ArrayView<const uint8_t> rtp_packet) {
|
||||
if (!IsPlaying()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RtpPacketReceived rtp_packet;
|
||||
rtp_packet.Parse(data, length);
|
||||
RtpPacketReceived rtp_packet_received;
|
||||
rtp_packet_received.Parse(rtp_packet.data(), rtp_packet.size());
|
||||
|
||||
// Set payload type's sampling rate before we feed it into ReceiveStatistics.
|
||||
{
|
||||
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
|
||||
// would mean that remote media endpoint is sending incorrect payload id
|
||||
// which can't be processed correctly especially on payload type id in
|
||||
// dynamic range.
|
||||
if (it == receive_codec_info_.end()) {
|
||||
RTC_DLOG(LS_WARNING) << "Unexpected payload id received: "
|
||||
<< rtp_packet.PayloadType();
|
||||
<< rtp_packet_received.PayloadType();
|
||||
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;
|
||||
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 ||
|
||||
(packet_length - header.headerLength) < header.paddingLength) {
|
||||
RTC_DLOG(LS_ERROR) << "Packet length(" << packet_length << ") header("
|
||||
@ -173,7 +154,7 @@ void AudioIngress::ReceivedRTPPacket(const uint8_t* data, size_t length) {
|
||||
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_data_length = payload_length - header.paddingLength;
|
||||
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) {
|
||||
// Deliver RTCP packet to RTP/RTCP module for parsing
|
||||
rtp_rtcp_->IncomingRtcpPacket(data, length);
|
||||
void AudioIngress::ReceivedRTCPPacket(
|
||||
rtc::ArrayView<const uint8_t> rtcp_packet) {
|
||||
// Deliver RTCP packet to RTP/RTCP module for parsing.
|
||||
rtp_rtcp_->IncomingRtcpPacket(rtcp_packet.data(), rtcp_packet.size());
|
||||
|
||||
int64_t rtt = GetRoundTripTime();
|
||||
if (rtt == -1) {
|
||||
@ -234,24 +216,4 @@ int64_t AudioIngress::GetRoundTripTime() {
|
||||
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
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#ifndef AUDIO_VOIP_AUDIO_INGRESS_H_
|
||||
#define AUDIO_VOIP_AUDIO_INGRESS_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@ -45,47 +46,68 @@ class AudioIngress : public AudioMixer::Source {
|
||||
public:
|
||||
AudioIngress(RtpRtcp* rtp_rtcp,
|
||||
Clock* clock,
|
||||
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory,
|
||||
std::unique_ptr<ReceiveStatistics> receive_statistics);
|
||||
ReceiveStatistics* receive_statistics,
|
||||
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory);
|
||||
~AudioIngress() override;
|
||||
|
||||
// Start or stop receiving operation of AudioIngress.
|
||||
void StartPlay();
|
||||
void StopPlay();
|
||||
void StartPlay() { playing_ = true; }
|
||||
void StopPlay() {
|
||||
playing_ = false;
|
||||
output_audio_level_.ResetLevelFullRange();
|
||||
}
|
||||
|
||||
// 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
|
||||
// key type (int) of the map is the payload type of SdpAudioFormat.
|
||||
void SetReceiveCodecs(const std::map<int, SdpAudioFormat>& codecs);
|
||||
|
||||
// APIs to handle received RTP/RTCP packets from caller.
|
||||
void ReceivedRTPPacket(const uint8_t* data, size_t length);
|
||||
void ReceivedRTCPPacket(const uint8_t* data, size_t length);
|
||||
void ReceivedRTPPacket(rtc::ArrayView<const uint8_t> rtp_packet);
|
||||
void ReceivedRTCPPacket(rtc::ArrayView<const uint8_t> rtcp_packet);
|
||||
|
||||
// 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.
|
||||
// Therefore, the return value will vary between 0 ~ 0xFFFF. This type of
|
||||
// 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
|
||||
// remote media endpoint. RTT value -1 indicates that it's not initialized.
|
||||
int64_t GetRoundTripTime();
|
||||
|
||||
NetworkStatistics GetNetworkStatistics() const;
|
||||
AudioDecodingCallStats GetDecodingStatistics() const;
|
||||
NetworkStatistics GetNetworkStatistics() 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.
|
||||
AudioMixer::Source::AudioFrameInfo GetAudioFrameWithInfo(
|
||||
int sampling_rate,
|
||||
AudioFrame* audio_frame) override;
|
||||
int Ssrc() const override;
|
||||
int PreferredSampleRate() const override;
|
||||
int Ssrc() 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:
|
||||
// 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
|
||||
// producing no data to output device.
|
||||
std::atomic<bool> playing_;
|
||||
@ -98,7 +120,7 @@ class AudioIngress : public AudioMixer::Source {
|
||||
std::atomic<int64_t> first_rtp_timestamp_;
|
||||
|
||||
// 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.
|
||||
RtpRtcp* const rtp_rtcp_;
|
||||
|
||||
@ -9,6 +9,42 @@
|
||||
import("../../../webrtc.gni")
|
||||
|
||||
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") {
|
||||
testonly = true
|
||||
sources = [ "audio_ingress_unittest.cc" ]
|
||||
|
||||
143
audio/voip/test/audio_channel_unittest.cc
Normal file
143
audio/voip/test/audio_channel_unittest.cc
Normal 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
|
||||
@ -76,6 +76,7 @@ class AudioEgressTest : public ::testing::Test {
|
||||
|
||||
// Make sure we have shut down rtp stack and reset egress for each test.
|
||||
void TearDown() override {
|
||||
egress_->StopSend();
|
||||
rtp_rtcp_->SetSendingStatus(false);
|
||||
egress_.reset();
|
||||
}
|
||||
@ -99,10 +100,10 @@ class AudioEgressTest : public ::testing::Test {
|
||||
SimulatedClock fake_clock_;
|
||||
NiceMock<MockTransport> transport_;
|
||||
SineWaveGenerator wave_generator_;
|
||||
std::unique_ptr<AudioEgress> egress_;
|
||||
std::unique_ptr<TaskQueueFactory> task_queue_factory_;
|
||||
std::unique_ptr<RtpRtcp> rtp_rtcp_;
|
||||
std::unique_ptr<TaskQueueFactory> task_queue_factory_;
|
||||
rtc::scoped_refptr<AudioEncoderFactory> encoder_factory_;
|
||||
std::unique_ptr<AudioEgress> egress_;
|
||||
};
|
||||
|
||||
TEST_F(AudioEgressTest, SendingStatusAfterStartAndStop) {
|
||||
|
||||
@ -30,26 +30,26 @@ using ::testing::Unused;
|
||||
|
||||
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 {
|
||||
public:
|
||||
const SdpAudioFormat kPcmuFormat = {"pcmu", 8000, 1};
|
||||
|
||||
AudioIngressTest()
|
||||
: 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();
|
||||
encoder_factory_ = CreateBuiltinAudioEncoderFactory();
|
||||
decoder_factory_ = CreateBuiltinAudioDecoderFactory();
|
||||
@ -57,9 +57,9 @@ class AudioIngressTest : public ::testing::Test {
|
||||
|
||||
void SetUp() override {
|
||||
constexpr int kPcmuPayload = 0;
|
||||
ingress_ = std::make_unique<AudioIngress>(
|
||||
rtp_rtcp_.get(), &fake_clock_, decoder_factory_,
|
||||
ReceiveStatistics::Create(&fake_clock_));
|
||||
ingress_ = std::make_unique<AudioIngress>(rtp_rtcp_.get(), &fake_clock_,
|
||||
receive_statistics_.get(),
|
||||
decoder_factory_);
|
||||
ingress_->SetReceiveCodecs({{kPcmuPayload, kPcmuFormat}});
|
||||
|
||||
egress_ = std::make_unique<AudioEgress>(rtp_rtcp_.get(), &fake_clock_,
|
||||
@ -76,6 +76,8 @@ class AudioIngressTest : public ::testing::Test {
|
||||
rtp_rtcp_->SetSendingStatus(false);
|
||||
ingress_->StopPlay();
|
||||
egress_->StopSend();
|
||||
egress_.reset();
|
||||
ingress_.reset();
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioFrame> GetAudioFrame(int order) {
|
||||
@ -91,25 +93,25 @@ class AudioIngressTest : public ::testing::Test {
|
||||
SimulatedClock fake_clock_;
|
||||
SineWaveGenerator wave_generator_;
|
||||
NiceMock<MockTransport> transport_;
|
||||
std::unique_ptr<AudioIngress> ingress_;
|
||||
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory_;
|
||||
// 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_;
|
||||
std::unique_ptr<ReceiveStatistics> receive_statistics_;
|
||||
std::unique_ptr<RtpRtcp> rtp_rtcp_;
|
||||
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) {
|
||||
EXPECT_EQ(ingress_->Playing(), true);
|
||||
EXPECT_EQ(ingress_->IsPlaying(), true);
|
||||
ingress_->StopPlay();
|
||||
EXPECT_EQ(ingress_->Playing(), false);
|
||||
EXPECT_EQ(ingress_->IsPlaying(), false);
|
||||
}
|
||||
|
||||
TEST_F(AudioIngressTest, GetAudioFrameAfterRtpReceived) {
|
||||
rtc::Event event;
|
||||
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();
|
||||
return true;
|
||||
};
|
||||
@ -137,7 +139,7 @@ TEST_F(AudioIngressTest, GetSpeechOutputLevelFullRange) {
|
||||
int rtp_count = 0;
|
||||
rtc::Event event;
|
||||
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) {
|
||||
event.Set();
|
||||
}
|
||||
@ -162,7 +164,7 @@ TEST_F(AudioIngressTest, GetSpeechOutputLevelFullRange) {
|
||||
TEST_F(AudioIngressTest, PreferredSampleRate) {
|
||||
rtc::Event event;
|
||||
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();
|
||||
return true;
|
||||
};
|
||||
|
||||
100
audio/voip/test/voip_core_unittest.cc
Normal file
100
audio/voip/test/voip_core_unittest.cc
Normal 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
348
audio/voip/voip_core.cc
Normal 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
139
audio/voip/voip_core.h
Normal 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_
|
||||
Loading…
x
Reference in New Issue
Block a user