diff --git a/api/voip/BUILD.gn b/api/voip/BUILD.gn index 6f92ed67f4..369a82f3aa 100644 --- a/api/voip/BUILD.gn +++ b/api/voip/BUILD.gn @@ -13,6 +13,7 @@ rtc_source_set("voip_api") { sources = [ "voip_base.h", "voip_codec.h", + "voip_dtmf.h", "voip_engine.h", "voip_network.h", ] diff --git a/api/voip/voip_dtmf.h b/api/voip/voip_dtmf.h new file mode 100644 index 0000000000..56817bae50 --- /dev/null +++ b/api/voip/voip_dtmf.h @@ -0,0 +1,67 @@ +/* + * 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_DTMF_H_ +#define API_VOIP_VOIP_DTMF_H_ + +#include "api/voip/voip_base.h" + +namespace webrtc { + +// DTMF events and their event codes as defined in +// https://tools.ietf.org/html/rfc4733#section-7 +enum class DtmfEvent : uint8_t { + kDigitZero = 0, + kDigitOne, + kDigitTwo, + kDigitThree, + kDigitFour, + kDigitFive, + kDigitSix, + kDigitSeven, + kDigitEight, + kDigitNine, + kAsterisk, + kHash, + kLetterA, + kLetterB, + kLetterC, + kLetterD +}; + +// VoipDtmf interface provides DTMF related interfaces such +// as sending DTMF events to the remote endpoint. +class VoipDtmf { + public: + // Register the payload type and sample rate for DTMF (RFC 4733) payload. + // Must be called exactly once prior to calling SendDtmfEvent after payload + // type has been negotiated with remote. + virtual void RegisterTelephoneEventType(ChannelId channel_id, + int rtp_payload_type, + int sample_rate_hz) = 0; + + // Send DTMF named event as specified by + // https://tools.ietf.org/html/rfc4733#section-3.2 + // |duration_ms| specifies the duration of DTMF packets that will be emitted + // in place of real RTP packets instead. + // Must be called after RegisterTelephoneEventType and VoipBase::StartSend + // have been called. + // Returns true if the requested DTMF event is successfully scheduled. + virtual bool SendDtmfEvent(ChannelId channel_id, + DtmfEvent dtmf_event, + int duration_ms) = 0; + + protected: + virtual ~VoipDtmf() = default; +}; + +} // namespace webrtc + +#endif // API_VOIP_VOIP_DTMF_H_ diff --git a/api/voip/voip_engine.h b/api/voip/voip_engine.h index 81c97c02e5..bff261f77a 100644 --- a/api/voip/voip_engine.h +++ b/api/voip/voip_engine.h @@ -16,6 +16,7 @@ namespace webrtc { class VoipBase; class VoipCodec; class VoipNetwork; +class VoipDtmf; // 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 @@ -80,6 +81,9 @@ class VoipEngine { // VoipCodec provides codec configuration APIs for encoder and decoders. virtual VoipCodec& Codec() = 0; + + // VoipDtmf provides DTMF event APIs to register and send DTMF events. + virtual VoipDtmf& Dtmf() = 0; }; } // namespace webrtc diff --git a/audio/voip/audio_channel.h b/audio/voip/audio_channel.h index 12138ee67c..04fbfe3a57 100644 --- a/audio/voip/audio_channel.h +++ b/audio/voip/audio_channel.h @@ -63,6 +63,12 @@ class AudioChannel : public rtc::RefCountInterface { absl::optional GetEncoderFormat() const { return egress_->GetEncoderFormat(); } + void RegisterTelephoneEventType(int rtp_payload_type, int sample_rate_hz) { + egress_->RegisterTelephoneEventType(rtp_payload_type, sample_rate_hz); + } + bool SendTelephoneEvent(int dtmf_event, int duration_ms) { + return egress_->SendTelephoneEvent(dtmf_event, duration_ms); + } // APIs relayed to AudioIngress. bool IsPlaying() const { return ingress_->IsPlaying(); } diff --git a/audio/voip/test/voip_core_unittest.cc b/audio/voip/test/voip_core_unittest.cc index b97b637dda..713f7f65b2 100644 --- a/audio/voip/test/voip_core_unittest.cc +++ b/audio/voip/test/voip_core_unittest.cc @@ -24,6 +24,9 @@ using ::testing::NiceMock; using ::testing::Return; constexpr int kPcmuPayload = 0; +constexpr int kPcmuSampleRateHz = 8000; +constexpr int kDtmfEventDurationMs = 1000; +constexpr DtmfEvent kDtmfEventCode = DtmfEvent::kDigitZero; class VoipCoreTest : public ::testing::Test { public: @@ -68,6 +71,12 @@ TEST_F(VoipCoreTest, BasicVoipCoreOperation) { EXPECT_TRUE(voip_core_->StartSend(*channel)); EXPECT_TRUE(voip_core_->StartPlayout(*channel)); + voip_core_->RegisterTelephoneEventType(*channel, kPcmuPayload, + kPcmuSampleRateHz); + + EXPECT_TRUE(voip_core_->SendDtmfEvent(*channel, kDtmfEventCode, + kDtmfEventDurationMs)); + // 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)); @@ -91,9 +100,52 @@ TEST_F(VoipCoreTest, ExpectFailToUseReleasedChannelId) { // These should be no-op. voip_core_->SetSendCodec(*channel, kPcmuPayload, kPcmuFormat); voip_core_->SetReceiveCodecs(*channel, {{kPcmuPayload, kPcmuFormat}}); + voip_core_->RegisterTelephoneEventType(*channel, kPcmuPayload, + kPcmuSampleRateHz); EXPECT_FALSE(voip_core_->StartSend(*channel)); EXPECT_FALSE(voip_core_->StartPlayout(*channel)); + EXPECT_FALSE(voip_core_->SendDtmfEvent(*channel, kDtmfEventCode, + kDtmfEventDurationMs)); +} + +TEST_F(VoipCoreTest, SendDtmfEventWithoutRegistering) { + // Program mock as non-operational and ready to start send. + EXPECT_CALL(*audio_device_, Recording()).WillOnce(Return(false)); + EXPECT_CALL(*audio_device_, InitRecording()).WillOnce(Return(0)); + EXPECT_CALL(*audio_device_, StartRecording()).WillOnce(Return(0)); + + auto channel = voip_core_->CreateChannel(&transport_, 0xdeadc0de); + EXPECT_TRUE(channel); + + voip_core_->SetSendCodec(*channel, kPcmuPayload, kPcmuFormat); + + EXPECT_TRUE(voip_core_->StartSend(*channel)); + // Send Dtmf event without registering beforehand, thus payload + // type is not set and false is expected. + EXPECT_FALSE(voip_core_->SendDtmfEvent(*channel, kDtmfEventCode, + kDtmfEventDurationMs)); + + // Program mock as sending and is ready to be stopped. + EXPECT_CALL(*audio_device_, Recording()).WillOnce(Return(true)); + EXPECT_CALL(*audio_device_, StopRecording()).WillOnce(Return(0)); + + EXPECT_TRUE(voip_core_->StopSend(*channel)); + voip_core_->ReleaseChannel(*channel); +} + +TEST_F(VoipCoreTest, SendDtmfEventWithoutStartSend) { + auto channel = voip_core_->CreateChannel(&transport_, 0xdeadc0de); + EXPECT_TRUE(channel); + + voip_core_->RegisterTelephoneEventType(*channel, kPcmuPayload, + kPcmuSampleRateHz); + // Send Dtmf event without calling StartSend beforehand, thus + // Dtmf events cannot be sent and false is expected. + EXPECT_FALSE(voip_core_->SendDtmfEvent(*channel, kDtmfEventCode, + kDtmfEventDurationMs)); + + voip_core_->ReleaseChannel(*channel); } TEST_F(VoipCoreTest, StartSendAndPlayoutWithoutSettingCodec) { diff --git a/audio/voip/voip_core.cc b/audio/voip/voip_core.cc index 639022363e..179fa51dfd 100644 --- a/audio/voip/voip_core.cc +++ b/audio/voip/voip_core.cc @@ -340,4 +340,24 @@ void VoipCore::SetReceiveCodecs( } } +void VoipCore::RegisterTelephoneEventType(ChannelId channel, + int rtp_payload_type, + int sample_rate_hz) { + // Failure to locate channel is logged internally in GetChannel. + if (auto audio_channel = GetChannel(channel)) { + audio_channel->RegisterTelephoneEventType(rtp_payload_type, sample_rate_hz); + } +} + +bool VoipCore::SendDtmfEvent(ChannelId channel, + DtmfEvent dtmf_event, + int duration_ms) { + // Failure to locate channel is logged internally in GetChannel. + if (auto audio_channel = GetChannel(channel)) { + return audio_channel->SendTelephoneEvent(static_cast(dtmf_event), + duration_ms); + } + return false; +} + } // namespace webrtc diff --git a/audio/voip/voip_core.h b/audio/voip/voip_core.h index 22a6559981..6654ff7d95 100644 --- a/audio/voip/voip_core.h +++ b/audio/voip/voip_core.h @@ -23,6 +23,7 @@ #include "api/task_queue/task_queue_factory.h" #include "api/voip/voip_base.h" #include "api/voip/voip_codec.h" +#include "api/voip/voip_dtmf.h" #include "api/voip/voip_engine.h" #include "api/voip/voip_network.h" #include "audio/audio_transport_impl.h" @@ -45,7 +46,8 @@ namespace webrtc { class VoipCore : public VoipEngine, public VoipBase, public VoipNetwork, - public VoipCodec { + public VoipCodec, + public VoipDtmf { public: ~VoipCore() override = default; @@ -63,6 +65,7 @@ class VoipCore : public VoipEngine, VoipBase& Base() override { return *this; } VoipNetwork& Network() override { return *this; } VoipCodec& Codec() override { return *this; } + VoipDtmf& Dtmf() override { return *this; } // Implements VoipBase interfaces. absl::optional CreateChannel( @@ -88,6 +91,14 @@ class VoipCore : public VoipEngine, ChannelId channel, const std::map& decoder_specs) override; + // Implements VoipDtmf interfaces. + void RegisterTelephoneEventType(ChannelId channel, + int rtp_payload_type, + int sample_rate_hz) override; + bool SendDtmfEvent(ChannelId channel, + DtmfEvent dtmf_event, + int duration_ms) override; + private: // Fetches the corresponding AudioChannel assigned with given |channel|. // Returns nullptr if not found.