ACM: Removing runtime APIs related to playout mode
The playout mode in NetEq can still be set through the constructor configuration. BUG=webrtc:3520 Review URL: https://codereview.webrtc.org/1362943004 Cr-Commit-Position: refs/heads/master@{#10089}
This commit is contained in:

committed by
Commit bot

parent
d417523194
commit
1bd0e03ce5
@ -213,51 +213,6 @@ int AcmReceiver::current_sample_rate_hz() const {
|
|||||||
return current_sample_rate_hz_;
|
return current_sample_rate_hz_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(turajs): use one set of enumerators, e.g. the one defined in
|
|
||||||
// common_types.h
|
|
||||||
// TODO(henrik.lundin): This method is not used any longer. The call hierarchy
|
|
||||||
// stops in voe::Channel::SetNetEQPlayoutMode(). Remove it.
|
|
||||||
void AcmReceiver::SetPlayoutMode(AudioPlayoutMode mode) {
|
|
||||||
enum NetEqPlayoutMode playout_mode = kPlayoutOn;
|
|
||||||
switch (mode) {
|
|
||||||
case voice:
|
|
||||||
playout_mode = kPlayoutOn;
|
|
||||||
break;
|
|
||||||
case fax: // No change to background noise mode.
|
|
||||||
playout_mode = kPlayoutFax;
|
|
||||||
break;
|
|
||||||
case streaming:
|
|
||||||
playout_mode = kPlayoutStreaming;
|
|
||||||
break;
|
|
||||||
case off:
|
|
||||||
playout_mode = kPlayoutOff;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
neteq_->SetPlayoutMode(playout_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioPlayoutMode AcmReceiver::PlayoutMode() const {
|
|
||||||
AudioPlayoutMode acm_mode = voice;
|
|
||||||
NetEqPlayoutMode mode = neteq_->PlayoutMode();
|
|
||||||
switch (mode) {
|
|
||||||
case kPlayoutOn:
|
|
||||||
acm_mode = voice;
|
|
||||||
break;
|
|
||||||
case kPlayoutOff:
|
|
||||||
acm_mode = off;
|
|
||||||
break;
|
|
||||||
case kPlayoutFax:
|
|
||||||
acm_mode = fax;
|
|
||||||
break;
|
|
||||||
case kPlayoutStreaming:
|
|
||||||
acm_mode = streaming;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
assert(false);
|
|
||||||
}
|
|
||||||
return acm_mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
int AcmReceiver::InsertPacket(const WebRtcRTPHeader& rtp_header,
|
int AcmReceiver::InsertPacket(const WebRtcRTPHeader& rtp_header,
|
||||||
const uint8_t* incoming_payload,
|
const uint8_t* incoming_payload,
|
||||||
size_t length_payload) {
|
size_t length_payload) {
|
||||||
|
@ -177,21 +177,6 @@ class AcmReceiver {
|
|||||||
//
|
//
|
||||||
int current_sample_rate_hz() const;
|
int current_sample_rate_hz() const;
|
||||||
|
|
||||||
//
|
|
||||||
// Sets the playout mode.
|
|
||||||
//
|
|
||||||
// Input:
|
|
||||||
// - mode : an enumerator specifying the playout mode.
|
|
||||||
//
|
|
||||||
void SetPlayoutMode(AudioPlayoutMode mode);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Get the current playout mode.
|
|
||||||
//
|
|
||||||
// Return value : The current playout mode.
|
|
||||||
//
|
|
||||||
AudioPlayoutMode PlayoutMode() const;
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Get the current network statistics from NetEq.
|
// Get the current network statistics from NetEq.
|
||||||
//
|
//
|
||||||
|
@ -265,21 +265,6 @@ TEST_F(AcmReceiverTest, DISABLED_ON_ANDROID(SampleRate)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that the playout mode is set correctly.
|
|
||||||
TEST_F(AcmReceiverTest, DISABLED_ON_ANDROID(PlayoutMode)) {
|
|
||||||
receiver_->SetPlayoutMode(voice);
|
|
||||||
EXPECT_EQ(voice, receiver_->PlayoutMode());
|
|
||||||
|
|
||||||
receiver_->SetPlayoutMode(streaming);
|
|
||||||
EXPECT_EQ(streaming, receiver_->PlayoutMode());
|
|
||||||
|
|
||||||
receiver_->SetPlayoutMode(fax);
|
|
||||||
EXPECT_EQ(fax, receiver_->PlayoutMode());
|
|
||||||
|
|
||||||
receiver_->SetPlayoutMode(off);
|
|
||||||
EXPECT_EQ(off, receiver_->PlayoutMode());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(AcmReceiverTest, DISABLED_ON_ANDROID(PostdecodingVad)) {
|
TEST_F(AcmReceiverTest, DISABLED_ON_ANDROID(PostdecodingVad)) {
|
||||||
receiver_->EnableVad();
|
receiver_->EnableVad();
|
||||||
EXPECT_TRUE(receiver_->vad_enabled());
|
EXPECT_TRUE(receiver_->vad_enabled());
|
||||||
|
@ -264,21 +264,6 @@ TEST_F(AcmReceiverTestOldApi, DISABLED_ON_ANDROID(SampleRate)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that the playout mode is set correctly.
|
|
||||||
TEST_F(AcmReceiverTestOldApi, DISABLED_ON_ANDROID(PlayoutMode)) {
|
|
||||||
receiver_->SetPlayoutMode(voice);
|
|
||||||
EXPECT_EQ(voice, receiver_->PlayoutMode());
|
|
||||||
|
|
||||||
receiver_->SetPlayoutMode(streaming);
|
|
||||||
EXPECT_EQ(streaming, receiver_->PlayoutMode());
|
|
||||||
|
|
||||||
receiver_->SetPlayoutMode(fax);
|
|
||||||
EXPECT_EQ(fax, receiver_->PlayoutMode());
|
|
||||||
|
|
||||||
receiver_->SetPlayoutMode(off);
|
|
||||||
EXPECT_EQ(off, receiver_->PlayoutMode());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(AcmReceiverTestOldApi, DISABLED_ON_ANDROID(PostdecodingVad)) {
|
TEST_F(AcmReceiverTestOldApi, DISABLED_ON_ANDROID(PostdecodingVad)) {
|
||||||
receiver_->EnableVad();
|
receiver_->EnableVad();
|
||||||
EXPECT_TRUE(receiver_->vad_enabled());
|
EXPECT_TRUE(receiver_->vad_enabled());
|
||||||
|
@ -659,17 +659,6 @@ int AudioCodingModuleImpl::SetMaximumPlayoutDelay(int time_ms) {
|
|||||||
return receiver_.SetMaximumDelay(time_ms);
|
return receiver_.SetMaximumDelay(time_ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set playout mode for: voice, fax, streaming or off.
|
|
||||||
int AudioCodingModuleImpl::SetPlayoutMode(AudioPlayoutMode mode) {
|
|
||||||
receiver_.SetPlayoutMode(mode);
|
|
||||||
return 0; // TODO(turajs): return value is for backward compatibility.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get playout mode voice, fax, streaming or off.
|
|
||||||
AudioPlayoutMode AudioCodingModuleImpl::PlayoutMode() const {
|
|
||||||
return receiver_.PlayoutMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get 10 milliseconds of raw audio data to play out.
|
// Get 10 milliseconds of raw audio data to play out.
|
||||||
// Automatic resample to the requested frequency.
|
// Automatic resample to the requested frequency.
|
||||||
int AudioCodingModuleImpl::PlayoutData10Ms(int desired_freq_hz,
|
int AudioCodingModuleImpl::PlayoutData10Ms(int desired_freq_hz,
|
||||||
|
@ -154,12 +154,6 @@ class AudioCodingModuleImpl final : public AudioCodingModule {
|
|||||||
// audio is accumulated in NetEq buffer, then starts decoding payloads.
|
// audio is accumulated in NetEq buffer, then starts decoding payloads.
|
||||||
int SetInitialPlayoutDelay(int delay_ms) override;
|
int SetInitialPlayoutDelay(int delay_ms) override;
|
||||||
|
|
||||||
// Set playout mode voice, fax.
|
|
||||||
int SetPlayoutMode(AudioPlayoutMode mode) override;
|
|
||||||
|
|
||||||
// Get playout mode voice, fax.
|
|
||||||
AudioPlayoutMode PlayoutMode() const override;
|
|
||||||
|
|
||||||
// Get playout timestamp.
|
// Get playout timestamp.
|
||||||
int PlayoutTimestamp(uint32_t* timestamp) override;
|
int PlayoutTimestamp(uint32_t* timestamp) override;
|
||||||
|
|
||||||
|
@ -605,43 +605,6 @@ class AudioCodingModule {
|
|||||||
// TODO(tlegrand): Change function to return the timestamp.
|
// TODO(tlegrand): Change function to return the timestamp.
|
||||||
virtual int32_t PlayoutTimestamp(uint32_t* timestamp) = 0;
|
virtual int32_t PlayoutTimestamp(uint32_t* timestamp) = 0;
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
// int32_t SetPlayoutMode()
|
|
||||||
// Call this API to set the playout mode. Playout mode could be optimized
|
|
||||||
// for i) voice, ii) FAX or iii) streaming. In Voice mode, NetEQ is
|
|
||||||
// optimized to deliver highest audio quality while maintaining a minimum
|
|
||||||
// delay. In FAX mode, NetEQ is optimized to have few delay changes as
|
|
||||||
// possible and maintain a constant delay, perhaps large relative to voice
|
|
||||||
// mode, to avoid PLC. In streaming mode, we tolerate a little more delay
|
|
||||||
// to achieve better jitter robustness.
|
|
||||||
//
|
|
||||||
// Input:
|
|
||||||
// -mode : playout mode. Possible inputs are:
|
|
||||||
// "voice",
|
|
||||||
// "fax" and
|
|
||||||
// "streaming".
|
|
||||||
//
|
|
||||||
// Return value:
|
|
||||||
// -1 if failed to set the mode,
|
|
||||||
// 0 if succeeding.
|
|
||||||
//
|
|
||||||
virtual int32_t SetPlayoutMode(const AudioPlayoutMode mode) = 0;
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
// AudioPlayoutMode PlayoutMode()
|
|
||||||
// Get playout mode, i.e. whether it is speech, FAX or streaming. See
|
|
||||||
// audio_coding_module_typedefs.h for definition of AudioPlayoutMode.
|
|
||||||
//
|
|
||||||
// Return value:
|
|
||||||
// voice: is for voice output,
|
|
||||||
// fax: a mode that is optimized for receiving FAX signals.
|
|
||||||
// In this mode NetEq tries to maintain a constant high
|
|
||||||
// delay to avoid PLC if possible.
|
|
||||||
// streaming: a mode that is suitable for streaming. In this mode we
|
|
||||||
// accept longer delay to improve jitter robustness.
|
|
||||||
//
|
|
||||||
virtual AudioPlayoutMode PlayoutMode() const = 0;
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// int32_t PlayoutData10Ms(
|
// int32_t PlayoutData10Ms(
|
||||||
// Get 10 milliseconds of raw audio data for playout, at the given sampling
|
// Get 10 milliseconds of raw audio data for playout, at the given sampling
|
||||||
|
@ -18,35 +18,6 @@
|
|||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
// enum AudioPlayoutMode
|
|
||||||
// An enumerator for different playout modes.
|
|
||||||
//
|
|
||||||
// -voice : This is the standard mode for VoIP calls. The trade-off
|
|
||||||
// between low delay and jitter robustness is optimized
|
|
||||||
// for high-quality two-way communication.
|
|
||||||
// NetEQs packet loss concealment and signal processing
|
|
||||||
// capabilities are fully employed.
|
|
||||||
// -fax : The fax mode is optimized for decodability of fax signals
|
|
||||||
// rather than for perceived audio quality. When this mode
|
|
||||||
// is selected, NetEQ will do as few delay changes as possible,
|
|
||||||
// trying to maintain a high and constant delay. Meanwhile,
|
|
||||||
// the packet loss concealment efforts are reduced.
|
|
||||||
//
|
|
||||||
// -streaming : In the case of one-way communication such as passive
|
|
||||||
// conference participant, a webinar, or a streaming application,
|
|
||||||
// this mode can be used to improve the jitter robustness at
|
|
||||||
// the cost of increased delay.
|
|
||||||
// -off : Turns off most of NetEQ's features. Stuffs zeros for lost
|
|
||||||
// packets and during buffer increases.
|
|
||||||
//
|
|
||||||
enum AudioPlayoutMode {
|
|
||||||
voice = 0,
|
|
||||||
fax = 1,
|
|
||||||
streaming = 2,
|
|
||||||
off = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// enum ACMSpeechType
|
// enum ACMSpeechType
|
||||||
// An enumerator for possible labels of a decoded frame.
|
// An enumerator for possible labels of a decoded frame.
|
||||||
|
@ -424,7 +424,7 @@ void APITest::RunTest(char thread) {
|
|||||||
{
|
{
|
||||||
WriteLockScoped cs(_apiTestRWLock);
|
WriteLockScoped cs(_apiTestRWLock);
|
||||||
if (thread == 'A') {
|
if (thread == 'A') {
|
||||||
_testNumA = (_testNumB + 1 + (rand() % 4)) % 5;
|
_testNumA = (_testNumB + 1 + (rand() % 3)) % 4;
|
||||||
testNum = _testNumA;
|
testNum = _testNumA;
|
||||||
|
|
||||||
_movingDot[_dotPositionA] = ' ';
|
_movingDot[_dotPositionA] = ' ';
|
||||||
@ -437,7 +437,7 @@ void APITest::RunTest(char thread) {
|
|||||||
_dotPositionA += _dotMoveDirectionA;
|
_dotPositionA += _dotMoveDirectionA;
|
||||||
_movingDot[_dotPositionA] = (_dotMoveDirectionA > 0) ? '>' : '<';
|
_movingDot[_dotPositionA] = (_dotMoveDirectionA > 0) ? '>' : '<';
|
||||||
} else {
|
} else {
|
||||||
_testNumB = (_testNumA + 1 + (rand() % 4)) % 5;
|
_testNumB = (_testNumA + 1 + (rand() % 3)) % 4;
|
||||||
testNum = _testNumB;
|
testNum = _testNumB;
|
||||||
|
|
||||||
_movingDot[_dotPositionB] = ' ';
|
_movingDot[_dotPositionB] = ' ';
|
||||||
@ -459,18 +459,15 @@ void APITest::RunTest(char thread) {
|
|||||||
ChangeCodec('A');
|
ChangeCodec('A');
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
TestPlayout('B');
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
if (!_randomTest) {
|
if (!_randomTest) {
|
||||||
fprintf(stdout, "\nTesting Delay ...\n");
|
fprintf(stdout, "\nTesting Delay ...\n");
|
||||||
}
|
}
|
||||||
TestDelay('A');
|
TestDelay('A');
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 2:
|
||||||
TestSendVAD('A');
|
TestSendVAD('A');
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 3:
|
||||||
TestRegisteration('A');
|
TestRegisteration('A');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -493,7 +490,6 @@ bool APITest::APIRunA() {
|
|||||||
} else {
|
} else {
|
||||||
CurrentCodec('A');
|
CurrentCodec('A');
|
||||||
ChangeCodec('A');
|
ChangeCodec('A');
|
||||||
TestPlayout('B');
|
|
||||||
if (_codecCntrA == 0) {
|
if (_codecCntrA == 0) {
|
||||||
fprintf(stdout, "\nTesting Delay ...\n");
|
fprintf(stdout, "\nTesting Delay ...\n");
|
||||||
TestDelay('A');
|
TestDelay('A');
|
||||||
@ -922,67 +918,6 @@ void APITest::TestRegisteration(char sendSide) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Playout Mode, background noise mode.
|
|
||||||
// Receiver Frequency, playout frequency.
|
|
||||||
void APITest::TestPlayout(char receiveSide) {
|
|
||||||
AudioCodingModule* receiveACM;
|
|
||||||
AudioPlayoutMode* playoutMode = NULL;
|
|
||||||
switch (receiveSide) {
|
|
||||||
case 'A': {
|
|
||||||
receiveACM = _acmA.get();
|
|
||||||
playoutMode = &_playoutModeA;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'B': {
|
|
||||||
receiveACM = _acmB.get();
|
|
||||||
playoutMode = &_playoutModeB;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
receiveACM = _acmA.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t receiveFreqHz = receiveACM->ReceiveFrequency();
|
|
||||||
int32_t playoutFreqHz = receiveACM->PlayoutFrequency();
|
|
||||||
|
|
||||||
CHECK_ERROR_MT(receiveFreqHz);
|
|
||||||
CHECK_ERROR_MT(playoutFreqHz);
|
|
||||||
|
|
||||||
|
|
||||||
char playoutString[25];
|
|
||||||
switch (*playoutMode) {
|
|
||||||
case voice: {
|
|
||||||
*playoutMode = fax;
|
|
||||||
strncpy(playoutString, "FAX", 25);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case fax: {
|
|
||||||
*playoutMode = streaming;
|
|
||||||
strncpy(playoutString, "Streaming", 25);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case streaming: {
|
|
||||||
*playoutMode = voice;
|
|
||||||
strncpy(playoutString, "Voice", 25);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
*playoutMode = voice;
|
|
||||||
strncpy(playoutString, "Voice", 25);
|
|
||||||
}
|
|
||||||
CHECK_ERROR_MT(receiveACM->SetPlayoutMode(*playoutMode));
|
|
||||||
playoutString[24] = '\0';
|
|
||||||
|
|
||||||
if (!_randomTest) {
|
|
||||||
fprintf(stdout, "\n");
|
|
||||||
fprintf(stdout, "In Side %c\n", receiveSide);
|
|
||||||
fprintf(stdout, "---------------------------------\n");
|
|
||||||
fprintf(stdout, "Receive Frequency....... %d Hz\n", receiveFreqHz);
|
|
||||||
fprintf(stdout, "Playout Frequency....... %d Hz\n", playoutFreqHz);
|
|
||||||
fprintf(stdout, "Audio Playout Mode...... %s\n", playoutString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void APITest::TestSendVAD(char side) {
|
void APITest::TestSendVAD(char side) {
|
||||||
if (_randomTest) {
|
if (_randomTest) {
|
||||||
return;
|
return;
|
||||||
|
@ -141,9 +141,6 @@ class APITest : public ACMTest {
|
|||||||
int32_t _minDelayB;
|
int32_t _minDelayB;
|
||||||
bool _payloadUsed[32];
|
bool _payloadUsed[32];
|
||||||
|
|
||||||
AudioPlayoutMode _playoutModeA;
|
|
||||||
AudioPlayoutMode _playoutModeB;
|
|
||||||
|
|
||||||
bool _verbose;
|
bool _verbose;
|
||||||
|
|
||||||
int _dotPositionA;
|
int _dotPositionA;
|
||||||
|
@ -32,10 +32,16 @@ namespace webrtc {
|
|||||||
|
|
||||||
TwoWayCommunication::TwoWayCommunication(int testMode)
|
TwoWayCommunication::TwoWayCommunication(int testMode)
|
||||||
: _acmA(AudioCodingModule::Create(1)),
|
: _acmA(AudioCodingModule::Create(1)),
|
||||||
_acmB(AudioCodingModule::Create(2)),
|
|
||||||
_acmRefA(AudioCodingModule::Create(3)),
|
_acmRefA(AudioCodingModule::Create(3)),
|
||||||
_acmRefB(AudioCodingModule::Create(4)),
|
_testMode(testMode) {
|
||||||
_testMode(testMode) {}
|
AudioCodingModule::Config config;
|
||||||
|
// The clicks will be more obvious in FAX mode. TODO(henrik.lundin) Really?
|
||||||
|
config.neteq_config.playout_mode = kPlayoutFax;
|
||||||
|
config.id = 2;
|
||||||
|
_acmB.reset(AudioCodingModule::Create(config));
|
||||||
|
config.id = 4;
|
||||||
|
_acmRefB.reset(AudioCodingModule::Create(config));
|
||||||
|
}
|
||||||
|
|
||||||
TwoWayCommunication::~TwoWayCommunication() {
|
TwoWayCommunication::~TwoWayCommunication() {
|
||||||
delete _channel_A2B;
|
delete _channel_A2B;
|
||||||
@ -159,11 +165,6 @@ void TwoWayCommunication::SetUp() {
|
|||||||
_channelRef_B2A = new Channel;
|
_channelRef_B2A = new Channel;
|
||||||
_acmRefB->RegisterTransportCallback(_channelRef_B2A);
|
_acmRefB->RegisterTransportCallback(_channelRef_B2A);
|
||||||
_channelRef_B2A->RegisterReceiverACM(_acmRefA.get());
|
_channelRef_B2A->RegisterReceiverACM(_acmRefA.get());
|
||||||
|
|
||||||
// The clicks will be more obvious when we
|
|
||||||
// are in FAX mode.
|
|
||||||
EXPECT_EQ(_acmB->SetPlayoutMode(fax), 0);
|
|
||||||
EXPECT_EQ(_acmRefB->SetPlayoutMode(fax), 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwoWayCommunication::SetUpAutotest() {
|
void TwoWayCommunication::SetUpAutotest() {
|
||||||
@ -233,11 +234,6 @@ void TwoWayCommunication::SetUpAutotest() {
|
|||||||
_channelRef_B2A = new Channel;
|
_channelRef_B2A = new Channel;
|
||||||
_acmRefB->RegisterTransportCallback(_channelRef_B2A);
|
_acmRefB->RegisterTransportCallback(_channelRef_B2A);
|
||||||
_channelRef_B2A->RegisterReceiverACM(_acmRefA.get());
|
_channelRef_B2A->RegisterReceiverACM(_acmRefA.get());
|
||||||
|
|
||||||
// The clicks will be more obvious when we
|
|
||||||
// are in FAX mode.
|
|
||||||
EXPECT_EQ(0, _acmB->SetPlayoutMode(fax));
|
|
||||||
EXPECT_EQ(0, _acmRefB->SetPlayoutMode(fax));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwoWayCommunication::Perform() {
|
void TwoWayCommunication::Perform() {
|
||||||
|
Reference in New Issue
Block a user