Audio egress implementation for initial voip api in api/voip.
For simplicity and flexibility on audio only API, it deemed to be better to trim off all audio unrelated logic to serve the purpose. Bug: webrtc:11251 Change-Id: I40e3eba2714c171f7c98b158303a7b3f744ceb78 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/169462 Reviewed-by: Per Åhgren <peah@webrtc.org> Reviewed-by: Patrik Höglund <phoglund@webrtc.org> Reviewed-by: Sebastian Jansson <srte@webrtc.org> Commit-Queue: Patrik Höglund <phoglund@webrtc.org> Cr-Commit-Position: refs/heads/master@{#30922}
This commit is contained in:
parent
fa097a2190
commit
8ab3c77c01
9
BUILD.gn
9
BUILD.gn
@ -40,6 +40,7 @@ if (!build_with_chromium) {
|
|||||||
":rtc_unittests",
|
":rtc_unittests",
|
||||||
":slow_tests",
|
":slow_tests",
|
||||||
":video_engine_tests",
|
":video_engine_tests",
|
||||||
|
":voip_unittests",
|
||||||
":webrtc_nonparallel_tests",
|
":webrtc_nonparallel_tests",
|
||||||
":webrtc_perf_tests",
|
":webrtc_perf_tests",
|
||||||
"common_audio:common_audio_unittests",
|
"common_audio:common_audio_unittests",
|
||||||
@ -673,6 +674,14 @@ if (rtc_include_tests) {
|
|||||||
shard_timeout = 900
|
shard_timeout = 900
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rtc_test("voip_unittests") {
|
||||||
|
testonly = true
|
||||||
|
deps = [
|
||||||
|
"audio/voip/test:audio_egress_unittests",
|
||||||
|
"test:test_main",
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---- Poisons ----
|
# ---- Poisons ----
|
||||||
|
|||||||
30
audio/voip/BUILD.gn
Normal file
30
audio/voip/BUILD.gn
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import("../../webrtc.gni")
|
||||||
|
|
||||||
|
rtc_library("audio_egress") {
|
||||||
|
sources = [
|
||||||
|
"audio_egress.cc",
|
||||||
|
"audio_egress.h",
|
||||||
|
]
|
||||||
|
deps = [
|
||||||
|
"../../api/audio_codecs:audio_codecs_api",
|
||||||
|
"../../api/task_queue",
|
||||||
|
"../../audio",
|
||||||
|
"../../audio/utility:audio_frame_operations",
|
||||||
|
"../../call:audio_sender_interface",
|
||||||
|
"../../modules/audio_coding",
|
||||||
|
"../../modules/rtp_rtcp",
|
||||||
|
"../../modules/rtp_rtcp:rtp_rtcp_format",
|
||||||
|
"../../rtc_base:logging",
|
||||||
|
"../../rtc_base:rtc_task_queue",
|
||||||
|
"../../rtc_base:thread_checker",
|
||||||
|
"../../rtc_base:timeutils",
|
||||||
|
]
|
||||||
|
}
|
||||||
186
audio/voip/audio_egress.cc
Normal file
186
audio/voip/audio_egress.cc
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
//
|
||||||
|
// 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_egress.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "rtc_base/logging.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
|
||||||
|
AudioEgress::AudioEgress(RtpRtcp* rtp_rtcp,
|
||||||
|
Clock* clock,
|
||||||
|
TaskQueueFactory* task_queue_factory)
|
||||||
|
: rtp_rtcp_(rtp_rtcp),
|
||||||
|
rtp_sender_audio_(clock, rtp_rtcp_->RtpSender()),
|
||||||
|
audio_coding_(AudioCodingModule::Create(AudioCodingModule::Config())),
|
||||||
|
encoder_queue_(task_queue_factory->CreateTaskQueue(
|
||||||
|
"AudioEncoder",
|
||||||
|
TaskQueueFactory::Priority::NORMAL)) {
|
||||||
|
audio_coding_->RegisterTransportCallback(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioEgress::~AudioEgress() {
|
||||||
|
audio_coding_->RegisterTransportCallback(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioEgress::IsSending() const {
|
||||||
|
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
|
||||||
|
return rtp_rtcp_->SendingMedia();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioEgress::SetEncoder(int payload_type,
|
||||||
|
const SdpAudioFormat& encoder_format,
|
||||||
|
std::unique_ptr<AudioEncoder> encoder) {
|
||||||
|
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
|
||||||
|
RTC_DCHECK_GE(payload_type, 0);
|
||||||
|
RTC_DCHECK_LE(payload_type, 127);
|
||||||
|
|
||||||
|
encoder_format_ = encoder_format;
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
rtp_rtcp_->RegisterSendPayloadFrequency(payload_type,
|
||||||
|
encoder->RtpTimestampRateHz());
|
||||||
|
rtp_sender_audio_.RegisterAudioPayload("audio", payload_type,
|
||||||
|
encoder->RtpTimestampRateHz(),
|
||||||
|
encoder->NumChannels(), 0);
|
||||||
|
|
||||||
|
audio_coding_->SetEncoder(std::move(encoder));
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::optional<SdpAudioFormat> AudioEgress::GetEncoderFormat() const {
|
||||||
|
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
|
||||||
|
return encoder_format_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioEgress::StartSend() {
|
||||||
|
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
|
||||||
|
|
||||||
|
rtp_rtcp_->SetSendingMediaStatus(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioEgress::StopSend() {
|
||||||
|
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
|
||||||
|
|
||||||
|
rtp_rtcp_->SetSendingMediaStatus(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioEgress::SendAudioData(std::unique_ptr<AudioFrame> audio_frame) {
|
||||||
|
RTC_DCHECK_GT(audio_frame->samples_per_channel_, 0);
|
||||||
|
RTC_DCHECK_LE(audio_frame->num_channels_, 8);
|
||||||
|
|
||||||
|
encoder_queue_.PostTask(
|
||||||
|
[this, audio_frame = std::move(audio_frame)]() mutable {
|
||||||
|
RTC_DCHECK_RUN_ON(&encoder_queue_);
|
||||||
|
if (!rtp_rtcp_->SendingMedia()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioFrameOperations::Mute(audio_frame.get(),
|
||||||
|
encoder_context_.previously_muted_,
|
||||||
|
encoder_context_.mute_);
|
||||||
|
encoder_context_.previously_muted_ = encoder_context_.mute_;
|
||||||
|
|
||||||
|
audio_frame->timestamp_ = encoder_context_.frame_rtp_timestamp_;
|
||||||
|
|
||||||
|
// This call will trigger AudioPacketizationCallback::SendData if
|
||||||
|
// encoding is done and payload is ready for packetization and
|
||||||
|
// transmission. Otherwise, it will return without invoking the
|
||||||
|
// callback.
|
||||||
|
if (audio_coding_->Add10MsData(*audio_frame) < 0) {
|
||||||
|
RTC_DLOG(LS_ERROR) << "ACM::Add10MsData() failed.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder_context_.frame_rtp_timestamp_ +=
|
||||||
|
rtc::dchecked_cast<uint32_t>(audio_frame->samples_per_channel_);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t AudioEgress::SendData(AudioFrameType frame_type,
|
||||||
|
uint8_t payload_type,
|
||||||
|
uint32_t timestamp,
|
||||||
|
const uint8_t* payload_data,
|
||||||
|
size_t payload_size) {
|
||||||
|
RTC_DCHECK_RUN_ON(&encoder_queue_);
|
||||||
|
|
||||||
|
rtc::ArrayView<const uint8_t> payload(payload_data, payload_size);
|
||||||
|
|
||||||
|
// Currently we don't get a capture time from downstream modules (ADM,
|
||||||
|
// AudioTransportImpl).
|
||||||
|
// TODO(natim@webrtc.org): Integrate once it's ready.
|
||||||
|
constexpr uint32_t kUndefinedCaptureTime = -1;
|
||||||
|
|
||||||
|
// Push data from ACM to RTP/RTCP-module to deliver audio frame for
|
||||||
|
// packetization.
|
||||||
|
if (!rtp_rtcp_->OnSendingRtpFrame(timestamp, kUndefinedCaptureTime,
|
||||||
|
payload_type,
|
||||||
|
/*force_sender_report=*/false)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t rtp_timestamp = timestamp + rtp_rtcp_->StartTimestamp();
|
||||||
|
|
||||||
|
// This call will trigger Transport::SendPacket() from the RTP/RTCP module.
|
||||||
|
if (!rtp_sender_audio_.SendAudio(frame_type, payload_type, rtp_timestamp,
|
||||||
|
payload.data(), payload.size())) {
|
||||||
|
RTC_DLOG(LS_ERROR)
|
||||||
|
<< "AudioEgress::SendData() failed to send data to RTP/RTCP module";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioEgress::RegisterTelephoneEventType(int rtp_payload_type,
|
||||||
|
int sample_rate_hz) {
|
||||||
|
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
|
||||||
|
RTC_DCHECK_GE(rtp_payload_type, 0);
|
||||||
|
RTC_DCHECK_LE(rtp_payload_type, 127);
|
||||||
|
|
||||||
|
rtp_rtcp_->RegisterSendPayloadFrequency(rtp_payload_type, sample_rate_hz);
|
||||||
|
rtp_sender_audio_.RegisterAudioPayload("telephone-event", rtp_payload_type,
|
||||||
|
sample_rate_hz, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioEgress::SendTelephoneEvent(int dtmf_event, int duration_ms) {
|
||||||
|
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
|
||||||
|
RTC_DCHECK_GE(dtmf_event, 0);
|
||||||
|
RTC_DCHECK_LE(dtmf_event, 255);
|
||||||
|
RTC_DCHECK_GE(duration_ms, 0);
|
||||||
|
RTC_DCHECK_LE(duration_ms, 65535);
|
||||||
|
|
||||||
|
if (!IsSending()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int kTelephoneEventAttenuationdB = 10;
|
||||||
|
|
||||||
|
if (rtp_sender_audio_.SendTelephoneEvent(dtmf_event, duration_ms,
|
||||||
|
kTelephoneEventAttenuationdB) != 0) {
|
||||||
|
RTC_DLOG(LS_ERROR) << "SendTelephoneEvent() failed to send event";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioEgress::SetMute(bool mute) {
|
||||||
|
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
|
||||||
|
|
||||||
|
encoder_queue_.PostTask([this, mute] {
|
||||||
|
RTC_DCHECK_RUN_ON(&encoder_queue_);
|
||||||
|
encoder_context_.mute_ = mute;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace webrtc
|
||||||
137
audio/voip/audio_egress.h
Normal file
137
audio/voip/audio_egress.h
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
//
|
||||||
|
// 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_EGRESS_H_
|
||||||
|
#define AUDIO_VOIP_AUDIO_EGRESS_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "api/audio_codecs/audio_format.h"
|
||||||
|
#include "api/task_queue/task_queue_factory.h"
|
||||||
|
#include "audio/utility/audio_frame_operations.h"
|
||||||
|
#include "call/audio_sender.h"
|
||||||
|
#include "modules/audio_coding/include/audio_coding_module.h"
|
||||||
|
#include "modules/rtp_rtcp/include/report_block_data.h"
|
||||||
|
#include "modules/rtp_rtcp/include/rtp_rtcp.h"
|
||||||
|
#include "modules/rtp_rtcp/source/rtp_sender_audio.h"
|
||||||
|
#include "rtc_base/task_queue.h"
|
||||||
|
#include "rtc_base/thread_checker.h"
|
||||||
|
#include "rtc_base/time_utils.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
|
||||||
|
// AudioEgress receives input samples from AudioDeviceModule via
|
||||||
|
// AudioTransportImpl through AudioSender interface. Once it encodes the sample
|
||||||
|
// via selected encoder through AudioPacketizationCallback interface, the
|
||||||
|
// encoded payload will be packetized by the RTP stack, resulting in ready to
|
||||||
|
// send RTP packet to remote endpoint.
|
||||||
|
//
|
||||||
|
// This class enforces single worker thread access by caller via SequenceChecker
|
||||||
|
// in debug mode as expected thread usage pattern. In order to minimize the hold
|
||||||
|
// on audio input thread from OS, TaskQueue is employed to encode and send RTP
|
||||||
|
// asynchrounously.
|
||||||
|
//
|
||||||
|
// Note that this class is originally based on ChannelSend in
|
||||||
|
// audio/channel_send.cc with non-audio related logic trimmed as aimed for
|
||||||
|
// smaller footprint.
|
||||||
|
class AudioEgress : public AudioSender, public AudioPacketizationCallback {
|
||||||
|
public:
|
||||||
|
AudioEgress(RtpRtcp* rtp_rtcp,
|
||||||
|
Clock* clock,
|
||||||
|
TaskQueueFactory* task_queue_factory);
|
||||||
|
~AudioEgress() override;
|
||||||
|
|
||||||
|
// Set the encoder format and payload type for AudioCodingModule.
|
||||||
|
// It's possible to change the encoder type during its active usage.
|
||||||
|
// |payload_type| must be the type that is negotiated with peer through
|
||||||
|
// offer/answer.
|
||||||
|
void SetEncoder(int payload_type,
|
||||||
|
const SdpAudioFormat& encoder_format,
|
||||||
|
std::unique_ptr<AudioEncoder> encoder);
|
||||||
|
|
||||||
|
// Start or stop sending operation of AudioEgress. This will start/stop
|
||||||
|
// the RTP stack also causes encoder queue thread to start/stop
|
||||||
|
// processing input audio samples.
|
||||||
|
void StartSend();
|
||||||
|
void StopSend();
|
||||||
|
|
||||||
|
// Query the state of the RTP stack. This returns true if StartSend()
|
||||||
|
// called and false if StopSend() is called.
|
||||||
|
bool IsSending() const;
|
||||||
|
|
||||||
|
// Enable or disable Mute state.
|
||||||
|
void SetMute(bool mute);
|
||||||
|
|
||||||
|
// Retrieve current encoder format info. This returns encoder format set
|
||||||
|
// by SetEncoder() and if encoder is not set, this will return nullopt.
|
||||||
|
absl::optional<SdpAudioFormat> GetEncoderFormat() const;
|
||||||
|
|
||||||
|
// Register the payload type and sample rate for DTMF (RFC 4733) payload.
|
||||||
|
void RegisterTelephoneEventType(int rtp_payload_type, int sample_rate_hz);
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// This will return true when requested dtmf event is successfully scheduled
|
||||||
|
// otherwise false when the dtmf queue reached maximum of 20 events.
|
||||||
|
bool SendTelephoneEvent(int dtmf_event, int duration_ms);
|
||||||
|
|
||||||
|
// Implementation of AudioSender interface.
|
||||||
|
void SendAudioData(std::unique_ptr<AudioFrame> audio_frame) override;
|
||||||
|
|
||||||
|
// Implementation of AudioPacketizationCallback interface.
|
||||||
|
int32_t SendData(AudioFrameType frame_type,
|
||||||
|
uint8_t payload_type,
|
||||||
|
uint32_t timestamp,
|
||||||
|
const uint8_t* payload_data,
|
||||||
|
size_t payload_size) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Ensure that single worker thread access.
|
||||||
|
SequenceChecker worker_thread_checker_;
|
||||||
|
|
||||||
|
// Current encoder format selected by caller.
|
||||||
|
absl::optional<SdpAudioFormat> encoder_format_
|
||||||
|
RTC_GUARDED_BY(worker_thread_checker_);
|
||||||
|
|
||||||
|
// Synchronization is handled internally by RtpRtcp.
|
||||||
|
RtpRtcp* const rtp_rtcp_;
|
||||||
|
|
||||||
|
// Synchronization is handled internally by RTPSenderAudio.
|
||||||
|
RTPSenderAudio rtp_sender_audio_;
|
||||||
|
|
||||||
|
// Synchronization is handled internally by AudioCodingModule.
|
||||||
|
const std::unique_ptr<AudioCodingModule> audio_coding_;
|
||||||
|
|
||||||
|
// Struct that holds all variables used by encoder task queue.
|
||||||
|
struct EncoderContext {
|
||||||
|
// Offset used to mark rtp timestamp in sample rate unit in
|
||||||
|
// newly received audio frame from AudioTransport.
|
||||||
|
uint32_t frame_rtp_timestamp_ = 0;
|
||||||
|
|
||||||
|
// Flag to track mute state from caller. |previously_muted_| is used to
|
||||||
|
// track previous state as part of input to AudioFrameOperations::Mute
|
||||||
|
// to implement fading effect when (un)mute is invoked.
|
||||||
|
bool mute_ = false;
|
||||||
|
bool previously_muted_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
EncoderContext encoder_context_ RTC_GUARDED_BY(encoder_queue_);
|
||||||
|
|
||||||
|
// Defined last to ensure that there are no running tasks when the other
|
||||||
|
// members are destroyed.
|
||||||
|
rtc::TaskQueue encoder_queue_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace webrtc
|
||||||
|
|
||||||
|
#endif // AUDIO_VOIP_AUDIO_EGRESS_H_
|
||||||
29
audio/voip/test/BUILD.gn
Normal file
29
audio/voip/test/BUILD.gn
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import("../../../webrtc.gni")
|
||||||
|
|
||||||
|
if (rtc_include_tests) {
|
||||||
|
rtc_library("audio_egress_unittests") {
|
||||||
|
testonly = true
|
||||||
|
sources = [ "audio_egress_unittest.cc" ]
|
||||||
|
deps = [
|
||||||
|
"..:audio_egress",
|
||||||
|
"../../../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_test_utils",
|
||||||
|
"../../../modules/rtp_rtcp:rtp_rtcp_format",
|
||||||
|
"../../../rtc_base:logging",
|
||||||
|
"../../../rtc_base:rtc_event",
|
||||||
|
"../../../test:mock_transport",
|
||||||
|
"../../../test:test_support",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
288
audio/voip/test/audio_egress_unittest.cc
Normal file
288
audio/voip/test/audio_egress_unittest.cc
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
//
|
||||||
|
// 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_egress.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/sine_wave_generator.h"
|
||||||
|
#include "modules/rtp_rtcp/source/rtp_packet_received.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;
|
||||||
|
|
||||||
|
std::unique_ptr<RtpRtcp> CreateRtpStack(Clock* clock,
|
||||||
|
Transport* transport,
|
||||||
|
uint32_t remote_ssrc) {
|
||||||
|
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 = remote_ssrc;
|
||||||
|
auto rtp_rtcp = RtpRtcp::Create(rtp_config);
|
||||||
|
rtp_rtcp->SetSendingMediaStatus(false);
|
||||||
|
rtp_rtcp->SetRTCPStatus(RtcpMode::kCompound);
|
||||||
|
return rtp_rtcp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AudioEgressTest configures audio egress by using Rtp Stack, fake clock,
|
||||||
|
// and task queue factory. Encoder factory is needed to create codec and
|
||||||
|
// configure the RTP stack in audio egress.
|
||||||
|
class AudioEgressTest : public ::testing::Test {
|
||||||
|
public:
|
||||||
|
static constexpr int16_t kAudioLevel = 3004; // Used for sine wave level.
|
||||||
|
static constexpr uint16_t kSeqNum = 12345;
|
||||||
|
static constexpr uint64_t kStartTime = 123456789;
|
||||||
|
static constexpr uint32_t kRemoteSsrc = 0xDEADBEEF;
|
||||||
|
const SdpAudioFormat kPcmuFormat = {"pcmu", 8000, 1};
|
||||||
|
|
||||||
|
AudioEgressTest()
|
||||||
|
: fake_clock_(kStartTime), wave_generator_(1000.0, kAudioLevel) {
|
||||||
|
rtp_rtcp_ = CreateRtpStack(&fake_clock_, &transport_, kRemoteSsrc);
|
||||||
|
task_queue_factory_ = CreateDefaultTaskQueueFactory();
|
||||||
|
encoder_factory_ = CreateBuiltinAudioEncoderFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare test on audio egress by using PCMu codec with specific
|
||||||
|
// sequence number and its status to be running.
|
||||||
|
void SetUp() override {
|
||||||
|
egress_ = std::make_unique<AudioEgress>(rtp_rtcp_.get(), &fake_clock_,
|
||||||
|
task_queue_factory_.get());
|
||||||
|
constexpr int kPcmuPayload = 0;
|
||||||
|
egress_->SetEncoder(kPcmuPayload, kPcmuFormat,
|
||||||
|
encoder_factory_->MakeAudioEncoder(
|
||||||
|
kPcmuPayload, kPcmuFormat, absl::nullopt));
|
||||||
|
egress_->StartSend();
|
||||||
|
rtp_rtcp_->SetSequenceNumber(kSeqNum);
|
||||||
|
rtp_rtcp_->SetSendingStatus(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we have shut down rtp stack and reset egress for each test.
|
||||||
|
void TearDown() override {
|
||||||
|
rtp_rtcp_->SetSendingStatus(false);
|
||||||
|
egress_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an audio frame prepared for pcmu encoding. Timestamp is
|
||||||
|
// increased per RTP specification which is the number of samples it contains.
|
||||||
|
// Wave generator writes sine wave which has expected high level set
|
||||||
|
// by kAudioLevel.
|
||||||
|
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 doesn't directly affect this testcase as the the
|
||||||
|
// AudioFrame's timestamp is driven by GetAudioFrame.
|
||||||
|
SimulatedClock fake_clock_;
|
||||||
|
NiceMock<MockTransport> transport_;
|
||||||
|
SineWaveGenerator wave_generator_;
|
||||||
|
std::unique_ptr<AudioEgress> egress_;
|
||||||
|
std::unique_ptr<TaskQueueFactory> task_queue_factory_;
|
||||||
|
std::unique_ptr<RtpRtcp> rtp_rtcp_;
|
||||||
|
rtc::scoped_refptr<AudioEncoderFactory> encoder_factory_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(AudioEgressTest, SendingStatusAfterStartAndStop) {
|
||||||
|
EXPECT_TRUE(egress_->IsSending());
|
||||||
|
egress_->StopSend();
|
||||||
|
EXPECT_FALSE(egress_->IsSending());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AudioEgressTest, ProcessAudioWithMute) {
|
||||||
|
constexpr int kExpected = 10;
|
||||||
|
rtc::Event event;
|
||||||
|
int rtp_count = 0;
|
||||||
|
RtpPacketReceived rtp;
|
||||||
|
auto rtp_sent = [&](const uint8_t* packet, size_t length, Unused) {
|
||||||
|
rtp.Parse(packet, length);
|
||||||
|
if (++rtp_count == kExpected) {
|
||||||
|
event.Set();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(rtp_sent));
|
||||||
|
|
||||||
|
egress_->SetMute(true);
|
||||||
|
|
||||||
|
// Two 10 ms audio frames will result in rtp packet with ptime 20.
|
||||||
|
for (size_t i = 0; i < kExpected * 2; i++) {
|
||||||
|
egress_->SendAudioData(GetAudioFrame(i));
|
||||||
|
fake_clock_.AdvanceTimeMilliseconds(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
event.Wait(/*ms=*/1000);
|
||||||
|
EXPECT_EQ(rtp_count, kExpected);
|
||||||
|
|
||||||
|
// we expect on pcmu payload to result in 255 for silenced payload
|
||||||
|
RTPHeader header;
|
||||||
|
rtp.GetHeader(&header);
|
||||||
|
size_t packet_length = rtp.size();
|
||||||
|
size_t payload_length = packet_length - header.headerLength;
|
||||||
|
size_t payload_data_length = payload_length - header.paddingLength;
|
||||||
|
const uint8_t* payload = rtp.data() + header.headerLength;
|
||||||
|
for (size_t i = 0; i < payload_data_length; ++i) {
|
||||||
|
EXPECT_EQ(*payload++, 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AudioEgressTest, ProcessAudioWithSineWave) {
|
||||||
|
constexpr int kExpected = 10;
|
||||||
|
rtc::Event event;
|
||||||
|
int rtp_count = 0;
|
||||||
|
RtpPacketReceived rtp;
|
||||||
|
auto rtp_sent = [&](const uint8_t* packet, size_t length, Unused) {
|
||||||
|
rtp.Parse(packet, length);
|
||||||
|
if (++rtp_count == kExpected) {
|
||||||
|
event.Set();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(rtp_sent));
|
||||||
|
|
||||||
|
// Two 10 ms audio frames will result in rtp packet with ptime 20.
|
||||||
|
for (size_t i = 0; i < kExpected * 2; i++) {
|
||||||
|
egress_->SendAudioData(GetAudioFrame(i));
|
||||||
|
fake_clock_.AdvanceTimeMilliseconds(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
event.Wait(/*ms=*/1000);
|
||||||
|
EXPECT_EQ(rtp_count, kExpected);
|
||||||
|
|
||||||
|
// we expect on pcmu to result in < 255 for payload with sine wave
|
||||||
|
RTPHeader header;
|
||||||
|
rtp.GetHeader(&header);
|
||||||
|
size_t packet_length = rtp.size();
|
||||||
|
size_t payload_length = packet_length - header.headerLength;
|
||||||
|
size_t payload_data_length = payload_length - header.paddingLength;
|
||||||
|
const uint8_t* payload = rtp.data() + header.headerLength;
|
||||||
|
for (size_t i = 0; i < payload_data_length; ++i) {
|
||||||
|
EXPECT_NE(*payload++, 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AudioEgressTest, SkipAudioEncodingAfterStopSend) {
|
||||||
|
constexpr int kExpected = 10;
|
||||||
|
rtc::Event event;
|
||||||
|
int rtp_count = 0;
|
||||||
|
auto rtp_sent = [&](const uint8_t* packet, size_t length, Unused) {
|
||||||
|
if (++rtp_count == kExpected) {
|
||||||
|
event.Set();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(rtp_sent));
|
||||||
|
|
||||||
|
// Two 10 ms audio frames will result in rtp packet with ptime 20.
|
||||||
|
for (size_t i = 0; i < kExpected * 2; i++) {
|
||||||
|
egress_->SendAudioData(GetAudioFrame(i));
|
||||||
|
fake_clock_.AdvanceTimeMilliseconds(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
event.Wait(/*ms=*/1000);
|
||||||
|
EXPECT_EQ(rtp_count, kExpected);
|
||||||
|
|
||||||
|
// Now stop send and yet feed more data.
|
||||||
|
egress_->StopSend();
|
||||||
|
|
||||||
|
// It should be safe to exit the test case while encoder_queue_ has
|
||||||
|
// outstanding data to process. We are making sure that this doesn't
|
||||||
|
// result in crahses or sanitizer errors due to remaining data.
|
||||||
|
for (size_t i = 0; i < kExpected * 2; i++) {
|
||||||
|
egress_->SendAudioData(GetAudioFrame(i));
|
||||||
|
fake_clock_.AdvanceTimeMilliseconds(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AudioEgressTest, ChangeEncoderFromPcmuToOpus) {
|
||||||
|
absl::optional<SdpAudioFormat> pcmu = egress_->GetEncoderFormat();
|
||||||
|
EXPECT_TRUE(pcmu);
|
||||||
|
EXPECT_EQ(pcmu->clockrate_hz, kPcmuFormat.clockrate_hz);
|
||||||
|
EXPECT_EQ(pcmu->num_channels, kPcmuFormat.num_channels);
|
||||||
|
|
||||||
|
constexpr int kOpusPayload = 120;
|
||||||
|
const SdpAudioFormat kOpusFormat = {"opus", 48000, 2};
|
||||||
|
|
||||||
|
egress_->SetEncoder(kOpusPayload, kOpusFormat,
|
||||||
|
encoder_factory_->MakeAudioEncoder(
|
||||||
|
kOpusPayload, kOpusFormat, absl::nullopt));
|
||||||
|
|
||||||
|
absl::optional<SdpAudioFormat> opus = egress_->GetEncoderFormat();
|
||||||
|
EXPECT_TRUE(opus);
|
||||||
|
EXPECT_EQ(opus->clockrate_hz, kOpusFormat.clockrate_hz);
|
||||||
|
EXPECT_EQ(opus->num_channels, kOpusFormat.num_channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AudioEgressTest, SendDTMF) {
|
||||||
|
constexpr int kExpected = 7;
|
||||||
|
constexpr int kPayloadType = 100;
|
||||||
|
constexpr int kDurationMs = 100;
|
||||||
|
constexpr int kSampleRate = 8000;
|
||||||
|
constexpr int kEvent = 3;
|
||||||
|
|
||||||
|
egress_->RegisterTelephoneEventType(kPayloadType, kSampleRate);
|
||||||
|
// 100 ms duration will produce total 7 DTMF
|
||||||
|
// 1 @ 20 ms, 2 @ 40 ms, 3 @ 60 ms, 4 @ 80 ms
|
||||||
|
// 5, 6, 7 @ 100 ms (last one sends 3 dtmf)
|
||||||
|
egress_->SendTelephoneEvent(kEvent, kDurationMs);
|
||||||
|
|
||||||
|
rtc::Event event;
|
||||||
|
int dtmf_count = 0;
|
||||||
|
auto is_dtmf = [&](RtpPacketReceived& rtp) {
|
||||||
|
return (rtp.PayloadType() == kPayloadType &&
|
||||||
|
rtp.SequenceNumber() == kSeqNum + dtmf_count &&
|
||||||
|
rtp.padding_size() == 0 && rtp.Marker() == (dtmf_count == 0) &&
|
||||||
|
rtp.Ssrc() == kRemoteSsrc);
|
||||||
|
};
|
||||||
|
|
||||||
|
// It's possible that we may have actual audio RTP packets along with
|
||||||
|
// DTMF packtets. We are only interested in the exact number of DTMF
|
||||||
|
// packets rtp stack is emitting.
|
||||||
|
auto rtp_sent = [&](const uint8_t* packet, size_t length, Unused) {
|
||||||
|
RtpPacketReceived rtp;
|
||||||
|
rtp.Parse(packet, length);
|
||||||
|
if (is_dtmf(rtp) && ++dtmf_count == kExpected) {
|
||||||
|
event.Set();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(rtp_sent));
|
||||||
|
|
||||||
|
// Two 10 ms audio frames will result in rtp packet with ptime 20.
|
||||||
|
for (size_t i = 0; i < kExpected * 2; i++) {
|
||||||
|
egress_->SendAudioData(GetAudioFrame(i));
|
||||||
|
fake_clock_.AdvanceTimeMilliseconds(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
event.Wait(/*ms=*/1000);
|
||||||
|
EXPECT_EQ(dtmf_count, kExpected);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace webrtc
|
||||||
Loading…
x
Reference in New Issue
Block a user