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") {
|
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",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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_
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
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.
|
* 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;
|
||||||
|
|||||||
@ -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
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 {
|
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;
|
||||||
|
|||||||
@ -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_;
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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_;
|
||||||
|
|||||||
@ -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" ]
|
||||||
|
|||||||
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.
|
// 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) {
|
||||||
|
|||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
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