Audio ingress implementation for voip api.
This is based on channel_receive.cc implementation where non-audio related logics are trimmed off for smaller footprint in size. Bug: webrtc:11251 Change-Id: I743c9f93f509fa6fcc12981fa73a6f01ce38348c Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/172821 Commit-Queue: Tim Na <natim@webrtc.org> Reviewed-by: Per Åhgren <peah@webrtc.org> Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org> Cr-Commit-Position: refs/heads/master@{#31117}
This commit is contained in:
1
BUILD.gn
1
BUILD.gn
@ -684,6 +684,7 @@ if (rtc_include_tests) {
|
||||
testonly = true
|
||||
deps = [
|
||||
"audio/voip/test:audio_egress_unittests",
|
||||
"audio/voip/test:audio_ingress_unittests",
|
||||
"test:test_main",
|
||||
]
|
||||
}
|
||||
|
||||
@ -8,6 +8,31 @@
|
||||
|
||||
import("../../webrtc.gni")
|
||||
|
||||
rtc_library("audio_ingress") {
|
||||
sources = [
|
||||
"audio_ingress.cc",
|
||||
"audio_ingress.h",
|
||||
]
|
||||
deps = [
|
||||
"../../api:array_view",
|
||||
"../../api:rtp_headers",
|
||||
"../../api:scoped_refptr",
|
||||
"../../api:transport_api",
|
||||
"../../api/audio:audio_mixer_api",
|
||||
"../../api/audio_codecs:audio_codecs_api",
|
||||
"../../audio",
|
||||
"../../audio/utility:audio_frame_operations",
|
||||
"../../modules/audio_coding",
|
||||
"../../modules/rtp_rtcp",
|
||||
"../../modules/rtp_rtcp:rtp_rtcp_format",
|
||||
"../../modules/utility",
|
||||
"../../rtc_base:criticalsection",
|
||||
"../../rtc_base:logging",
|
||||
"../../rtc_base:safe_minmax",
|
||||
"../../rtc_base:timeutils",
|
||||
]
|
||||
}
|
||||
|
||||
rtc_library("audio_egress") {
|
||||
sources = [
|
||||
"audio_egress.cc",
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
//
|
||||
// Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the LICENSE file in the root of the source
|
||||
// tree. An additional intellectual property rights grant can be found
|
||||
// in the file PATENTS. All contributing project authors may
|
||||
// be found in the AUTHORS file in the root of the source tree.
|
||||
//
|
||||
/*
|
||||
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "audio/voip/audio_egress.h"
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
//
|
||||
// Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the LICENSE file in the root of the source
|
||||
// tree. An additional intellectual property rights grant can be found
|
||||
// in the file PATENTS. All contributing project authors may
|
||||
// be found in the AUTHORS file in the root of the source tree.
|
||||
//
|
||||
/*
|
||||
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_VOIP_AUDIO_EGRESS_H_
|
||||
#define AUDIO_VOIP_AUDIO_EGRESS_H_
|
||||
|
||||
257
audio/voip/audio_ingress.cc
Normal file
257
audio/voip/audio_ingress.cc
Normal file
@ -0,0 +1,257 @@
|
||||
/*
|
||||
* 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_ingress.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "api/audio_codecs/audio_format.h"
|
||||
#include "audio/utility/audio_frame_operations.h"
|
||||
#include "modules/audio_coding/include/audio_coding_module.h"
|
||||
#include "rtc_base/critical_section.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/numerics/safe_minmax.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
AudioCodingModule::Config CreateAcmConfig(
|
||||
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory) {
|
||||
AudioCodingModule::Config acm_config;
|
||||
acm_config.neteq_config.enable_muted_state = true;
|
||||
acm_config.decoder_factory = decoder_factory;
|
||||
return acm_config;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AudioIngress::AudioIngress(
|
||||
RtpRtcp* rtp_rtcp,
|
||||
Clock* clock,
|
||||
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory,
|
||||
std::unique_ptr<ReceiveStatistics> receive_statistics)
|
||||
: playing_(false),
|
||||
remote_ssrc_(0),
|
||||
first_rtp_timestamp_(-1),
|
||||
rtp_receive_statistics_(std::move(receive_statistics)),
|
||||
rtp_rtcp_(rtp_rtcp),
|
||||
acm_receiver_(CreateAcmConfig(decoder_factory)),
|
||||
ntp_estimator_(clock) {}
|
||||
|
||||
AudioIngress::~AudioIngress() = default;
|
||||
|
||||
void AudioIngress::StartPlay() {
|
||||
playing_ = true;
|
||||
}
|
||||
|
||||
void AudioIngress::StopPlay() {
|
||||
playing_ = false;
|
||||
output_audio_level_.ResetLevelFullRange();
|
||||
}
|
||||
|
||||
AudioMixer::Source::AudioFrameInfo AudioIngress::GetAudioFrameWithInfo(
|
||||
int sampling_rate,
|
||||
AudioFrame* audio_frame) {
|
||||
audio_frame->sample_rate_hz_ = sampling_rate;
|
||||
|
||||
// Get 10ms raw PCM data from the ACM.
|
||||
bool muted = false;
|
||||
if (acm_receiver_.GetAudio(sampling_rate, audio_frame, &muted) == -1) {
|
||||
RTC_DLOG(LS_ERROR) << "GetAudio() failed!";
|
||||
// In all likelihood, the audio in this frame is garbage. We return an
|
||||
// error so that the audio mixer module doesn't add it to the mix. As
|
||||
// a result, it won't be played out and the actions skipped here are
|
||||
// irrelevant.
|
||||
return AudioMixer::Source::AudioFrameInfo::kError;
|
||||
}
|
||||
|
||||
if (muted) {
|
||||
AudioFrameOperations::Mute(audio_frame);
|
||||
}
|
||||
|
||||
// Measure audio level.
|
||||
constexpr double kAudioSampleDurationSeconds = 0.01;
|
||||
output_audio_level_.ComputeLevel(*audio_frame, kAudioSampleDurationSeconds);
|
||||
|
||||
// Set first rtp timestamp with first audio frame with valid timestamp.
|
||||
if (first_rtp_timestamp_ < 0 && audio_frame->timestamp_ != 0) {
|
||||
first_rtp_timestamp_ = audio_frame->timestamp_;
|
||||
}
|
||||
|
||||
if (first_rtp_timestamp_ >= 0) {
|
||||
// Compute elapsed and NTP times.
|
||||
int64_t unwrap_timestamp;
|
||||
{
|
||||
rtc::CritScope lock(&lock_);
|
||||
unwrap_timestamp =
|
||||
timestamp_wrap_handler_.Unwrap(audio_frame->timestamp_);
|
||||
audio_frame->ntp_time_ms_ =
|
||||
ntp_estimator_.Estimate(audio_frame->timestamp_);
|
||||
}
|
||||
// For clock rate, default to the playout sampling rate if we haven't
|
||||
// received any packets yet.
|
||||
absl::optional<std::pair<int, SdpAudioFormat>> decoder =
|
||||
acm_receiver_.LastDecoder();
|
||||
int clock_rate = decoder ? decoder->second.clockrate_hz
|
||||
: acm_receiver_.last_output_sample_rate_hz();
|
||||
RTC_DCHECK_GT(clock_rate, 0);
|
||||
audio_frame->elapsed_time_ms_ =
|
||||
(unwrap_timestamp - first_rtp_timestamp_) / (clock_rate / 1000);
|
||||
}
|
||||
|
||||
return muted ? AudioMixer::Source::AudioFrameInfo::kMuted
|
||||
: AudioMixer::Source::AudioFrameInfo::kNormal;
|
||||
}
|
||||
|
||||
int AudioIngress::Ssrc() const {
|
||||
return rtc::dchecked_cast<int>(remote_ssrc_.load());
|
||||
}
|
||||
|
||||
int AudioIngress::PreferredSampleRate() const {
|
||||
// Return the bigger of playout and receive frequency in the ACM. Note that
|
||||
// return 0 means anything higher shouldn't cause any quality loss.
|
||||
return std::max(acm_receiver_.last_packet_sample_rate_hz().value_or(0),
|
||||
acm_receiver_.last_output_sample_rate_hz());
|
||||
}
|
||||
|
||||
void AudioIngress::SetReceiveCodecs(
|
||||
const std::map<int, SdpAudioFormat>& codecs) {
|
||||
{
|
||||
rtc::CritScope lock(&lock_);
|
||||
for (const auto& kv : codecs) {
|
||||
receive_codec_info_[kv.first] = kv.second.clockrate_hz;
|
||||
}
|
||||
}
|
||||
acm_receiver_.SetCodecs(codecs);
|
||||
}
|
||||
|
||||
void AudioIngress::ReceivedRTPPacket(const uint8_t* data, size_t length) {
|
||||
if (!Playing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RtpPacketReceived rtp_packet;
|
||||
rtp_packet.Parse(data, length);
|
||||
|
||||
// Set payload type's sampling rate before we feed it into ReceiveStatistics.
|
||||
{
|
||||
rtc::CritScope lock(&lock_);
|
||||
const auto& it = receive_codec_info_.find(rtp_packet.PayloadType());
|
||||
// If sampling rate info is not available in our received codec set, it
|
||||
// would mean that remote media endpoint is sending incorrect payload id
|
||||
// which can't be processed correctly especially on payload type id in
|
||||
// dynamic range.
|
||||
if (it == receive_codec_info_.end()) {
|
||||
RTC_DLOG(LS_WARNING) << "Unexpected payload id received: "
|
||||
<< rtp_packet.PayloadType();
|
||||
return;
|
||||
}
|
||||
rtp_packet.set_payload_type_frequency(it->second);
|
||||
}
|
||||
|
||||
rtp_receive_statistics_->OnRtpPacket(rtp_packet);
|
||||
|
||||
RTPHeader header;
|
||||
rtp_packet.GetHeader(&header);
|
||||
|
||||
size_t packet_length = rtp_packet.size();
|
||||
if (packet_length < header.headerLength ||
|
||||
(packet_length - header.headerLength) < header.paddingLength) {
|
||||
RTC_DLOG(LS_ERROR) << "Packet length(" << packet_length << ") header("
|
||||
<< header.headerLength << ") padding("
|
||||
<< header.paddingLength << ")";
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t* payload = rtp_packet.data() + header.headerLength;
|
||||
size_t payload_length = packet_length - header.headerLength;
|
||||
size_t payload_data_length = payload_length - header.paddingLength;
|
||||
auto data_view = rtc::ArrayView<const uint8_t>(payload, payload_data_length);
|
||||
|
||||
// Push the incoming payload (parsed and ready for decoding) into the ACM.
|
||||
if (acm_receiver_.InsertPacket(header, data_view) != 0) {
|
||||
RTC_DLOG(LS_ERROR) << "AudioIngress::ReceivedRTPPacket() unable to "
|
||||
"push data to the ACM";
|
||||
}
|
||||
}
|
||||
|
||||
void AudioIngress::ReceivedRTCPPacket(const uint8_t* data, size_t length) {
|
||||
// Deliver RTCP packet to RTP/RTCP module for parsing
|
||||
rtp_rtcp_->IncomingRtcpPacket(data, length);
|
||||
|
||||
int64_t rtt = GetRoundTripTime();
|
||||
if (rtt == -1) {
|
||||
// Waiting for valid RTT.
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t ntp_secs = 0, ntp_frac = 0, rtp_timestamp = 0;
|
||||
if (rtp_rtcp_->RemoteNTP(&ntp_secs, &ntp_frac, nullptr, nullptr,
|
||||
&rtp_timestamp) != 0) {
|
||||
// Waiting for RTCP.
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
rtc::CritScope lock(&lock_);
|
||||
ntp_estimator_.UpdateRtcpTimestamp(rtt, ntp_secs, ntp_frac, rtp_timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
int64_t AudioIngress::GetRoundTripTime() {
|
||||
const std::vector<ReportBlockData>& report_data =
|
||||
rtp_rtcp_->GetLatestReportBlockData();
|
||||
|
||||
// If we do not have report block which means remote RTCP hasn't be received
|
||||
// yet, return -1 as to indicate uninitialized value.
|
||||
if (report_data.empty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// We don't know in advance the remote SSRC used by the other end's receiver
|
||||
// reports, so use the SSRC of the first report block as remote SSRC for now.
|
||||
// TODO(natim@webrtc.org): handle the case where remote end is changing ssrc
|
||||
// and update accordingly here.
|
||||
const ReportBlockData& block_data = report_data[0];
|
||||
|
||||
const uint32_t sender_ssrc = block_data.report_block().sender_ssrc;
|
||||
|
||||
if (sender_ssrc != remote_ssrc_.load()) {
|
||||
remote_ssrc_.store(sender_ssrc);
|
||||
rtp_rtcp_->SetRemoteSSRC(sender_ssrc);
|
||||
}
|
||||
|
||||
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
|
||||
125
audio/voip/audio_ingress.h
Normal file
125
audio/voip/audio_ingress.h
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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_INGRESS_H_
|
||||
#define AUDIO_VOIP_AUDIO_INGRESS_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "api/array_view.h"
|
||||
#include "api/audio/audio_mixer.h"
|
||||
#include "api/rtp_headers.h"
|
||||
#include "api/scoped_refptr.h"
|
||||
#include "audio/audio_level.h"
|
||||
#include "modules/audio_coding/acm2/acm_receiver.h"
|
||||
#include "modules/audio_coding/include/audio_coding_module.h"
|
||||
#include "modules/rtp_rtcp/include/receive_statistics.h"
|
||||
#include "modules/rtp_rtcp/include/remote_ntp_time_estimator.h"
|
||||
#include "modules/rtp_rtcp/include/rtp_rtcp.h"
|
||||
#include "modules/rtp_rtcp/source/rtp_packet_received.h"
|
||||
#include "rtc_base/critical_section.h"
|
||||
#include "rtc_base/time_utils.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// AudioIngress handles incoming RTP/RTCP packets from the remote
|
||||
// media endpoint. Received RTP packets are injected into AcmReceiver and
|
||||
// when audio output thread requests for audio samples to play through system
|
||||
// output such as speaker device, AudioIngress provides the samples via its
|
||||
// implementation on AudioMixer::Source interface.
|
||||
//
|
||||
// Note that this class is originally based on ChannelReceive in
|
||||
// audio/channel_receive.cc with non-audio related logic trimmed as aimed for
|
||||
// smaller footprint.
|
||||
class AudioIngress : public AudioMixer::Source {
|
||||
public:
|
||||
AudioIngress(RtpRtcp* rtp_rtcp,
|
||||
Clock* clock,
|
||||
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory,
|
||||
std::unique_ptr<ReceiveStatistics> receive_statistics);
|
||||
~AudioIngress() override;
|
||||
|
||||
// Start or stop receiving operation of AudioIngress.
|
||||
void StartPlay();
|
||||
void StopPlay();
|
||||
|
||||
// Query the state of the AudioIngress.
|
||||
bool Playing() const;
|
||||
|
||||
// Set the decoder formats and payload type for AcmReceiver where the
|
||||
// key type (int) of the map is the payload type of SdpAudioFormat.
|
||||
void SetReceiveCodecs(const std::map<int, SdpAudioFormat>& codecs);
|
||||
|
||||
// APIs to handle received RTP/RTCP packets from caller.
|
||||
void ReceivedRTPPacket(const uint8_t* data, size_t length);
|
||||
void ReceivedRTCPPacket(const uint8_t* data, size_t length);
|
||||
|
||||
// Retrieve highest speech output level in last 100 ms. Note that
|
||||
// this isn't RMS but absolute raw audio level on int16_t sample unit.
|
||||
// Therefore, the return value will vary between 0 ~ 0xFFFF. This type of
|
||||
// value may be useful to be used for measuring active speaker gauge.
|
||||
int GetSpeechOutputLevelFullRange() const;
|
||||
|
||||
// Returns network round trip time (RTT) measued by RTCP exchange with
|
||||
// remote media endpoint. RTT value -1 indicates that it's not initialized.
|
||||
int64_t GetRoundTripTime();
|
||||
|
||||
NetworkStatistics GetNetworkStatistics() const;
|
||||
AudioDecodingCallStats GetDecodingStatistics() const;
|
||||
|
||||
// Implementation of AudioMixer::Source interface.
|
||||
AudioMixer::Source::AudioFrameInfo GetAudioFrameWithInfo(
|
||||
int sampling_rate,
|
||||
AudioFrame* audio_frame) override;
|
||||
int Ssrc() const override;
|
||||
int PreferredSampleRate() const override;
|
||||
|
||||
private:
|
||||
// Indicate AudioIngress status as caller invokes Start/StopPlaying.
|
||||
// If not playing, incoming RTP data processing is skipped, thus
|
||||
// producing no data to output device.
|
||||
std::atomic<bool> playing_;
|
||||
|
||||
// Currently active remote ssrc from remote media endpoint.
|
||||
std::atomic<uint32_t> remote_ssrc_;
|
||||
|
||||
// The first rtp timestamp of the output audio frame that is used to
|
||||
// calculate elasped time for subsequent audio frames.
|
||||
std::atomic<int64_t> first_rtp_timestamp_;
|
||||
|
||||
// Synchronizaton is handled internally by ReceiveStatistics.
|
||||
const std::unique_ptr<ReceiveStatistics> rtp_receive_statistics_;
|
||||
|
||||
// Synchronizaton is handled internally by RtpRtcp.
|
||||
RtpRtcp* const rtp_rtcp_;
|
||||
|
||||
// Synchronizaton is handled internally by acm2::AcmReceiver.
|
||||
acm2::AcmReceiver acm_receiver_;
|
||||
|
||||
// Synchronizaton is handled internally by voe::AudioLevel.
|
||||
voe::AudioLevel output_audio_level_;
|
||||
|
||||
rtc::CriticalSection lock_;
|
||||
|
||||
RemoteNtpTimeEstimator ntp_estimator_ RTC_GUARDED_BY(lock_);
|
||||
|
||||
// For receiving RTP statistics, this tracks the sampling rate value
|
||||
// per payload type set when caller set via SetReceiveCodecs.
|
||||
std::map<int, int> receive_codec_info_ RTC_GUARDED_BY(lock_);
|
||||
|
||||
rtc::TimestampWrapAroundHandler timestamp_wrap_handler_ RTC_GUARDED_BY(lock_);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // AUDIO_VOIP_AUDIO_INGRESS_H_
|
||||
@ -9,13 +9,30 @@
|
||||
import("../../../webrtc.gni")
|
||||
|
||||
if (rtc_include_tests) {
|
||||
rtc_library("audio_ingress_unittests") {
|
||||
testonly = true
|
||||
sources = [ "audio_ingress_unittest.cc" ]
|
||||
deps = [
|
||||
"..:audio_egress",
|
||||
"..:audio_ingress",
|
||||
"../../../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",
|
||||
"../../../rtc_base:logging",
|
||||
"../../../rtc_base:rtc_event",
|
||||
"../../../test:mock_transport",
|
||||
"../../../test:test_support",
|
||||
]
|
||||
}
|
||||
|
||||
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",
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
//
|
||||
// Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the LICENSE file in the root of the source
|
||||
// tree. An additional intellectual property rights grant can be found
|
||||
// in the file PATENTS. All contributing project authors may
|
||||
// be found in the AUTHORS file in the root of the source tree.
|
||||
//
|
||||
/*
|
||||
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "audio/voip/audio_egress.h"
|
||||
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
|
||||
|
||||
182
audio/voip/test/audio_ingress_unittest.cc
Normal file
182
audio/voip/test/audio_ingress_unittest.cc
Normal file
@ -0,0 +1,182 @@
|
||||
/*
|
||||
* 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_ingress.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 "audio/voip/audio_egress.h"
|
||||
#include "modules/audio_mixer/sine_wave_generator.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 int16_t kAudioLevel = 3004; // Used for sine wave level.
|
||||
|
||||
std::unique_ptr<RtpRtcp> CreateRtpStack(Clock* clock, Transport* transport) {
|
||||
RtpRtcp::Configuration rtp_config;
|
||||
rtp_config.clock = clock;
|
||||
rtp_config.audio = true;
|
||||
rtp_config.rtcp_report_interval_ms = 5000;
|
||||
rtp_config.outgoing_transport = transport;
|
||||
rtp_config.local_media_ssrc = 0xdeadc0de;
|
||||
auto rtp_rtcp = RtpRtcp::Create(rtp_config);
|
||||
rtp_rtcp->SetSendingMediaStatus(false);
|
||||
rtp_rtcp->SetRTCPStatus(RtcpMode::kCompound);
|
||||
return rtp_rtcp;
|
||||
}
|
||||
|
||||
class AudioIngressTest : public ::testing::Test {
|
||||
public:
|
||||
const SdpAudioFormat kPcmuFormat = {"pcmu", 8000, 1};
|
||||
|
||||
AudioIngressTest()
|
||||
: fake_clock_(123456789), wave_generator_(1000.0, kAudioLevel) {
|
||||
rtp_rtcp_ = CreateRtpStack(&fake_clock_, &transport_);
|
||||
task_queue_factory_ = CreateDefaultTaskQueueFactory();
|
||||
encoder_factory_ = CreateBuiltinAudioEncoderFactory();
|
||||
decoder_factory_ = CreateBuiltinAudioDecoderFactory();
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
constexpr int kPcmuPayload = 0;
|
||||
ingress_ = std::make_unique<AudioIngress>(
|
||||
rtp_rtcp_.get(), &fake_clock_, decoder_factory_,
|
||||
ReceiveStatistics::Create(&fake_clock_));
|
||||
ingress_->SetReceiveCodecs({{kPcmuPayload, kPcmuFormat}});
|
||||
|
||||
egress_ = std::make_unique<AudioEgress>(rtp_rtcp_.get(), &fake_clock_,
|
||||
task_queue_factory_.get());
|
||||
egress_->SetEncoder(kPcmuPayload, kPcmuFormat,
|
||||
encoder_factory_->MakeAudioEncoder(
|
||||
kPcmuPayload, kPcmuFormat, absl::nullopt));
|
||||
egress_->StartSend();
|
||||
ingress_->StartPlay();
|
||||
rtp_rtcp_->SetSendingStatus(true);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
rtp_rtcp_->SetSendingStatus(false);
|
||||
ingress_->StopPlay();
|
||||
egress_->StopSend();
|
||||
}
|
||||
|
||||
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<AudioIngress> ingress_;
|
||||
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory_;
|
||||
// Members used to drive the input to ingress.
|
||||
std::unique_ptr<AudioEgress> egress_;
|
||||
std::unique_ptr<TaskQueueFactory> task_queue_factory_;
|
||||
std::shared_ptr<RtpRtcp> rtp_rtcp_;
|
||||
rtc::scoped_refptr<AudioEncoderFactory> encoder_factory_;
|
||||
};
|
||||
|
||||
TEST_F(AudioIngressTest, PlayingAfterStartAndStop) {
|
||||
EXPECT_EQ(ingress_->Playing(), true);
|
||||
ingress_->StopPlay();
|
||||
EXPECT_EQ(ingress_->Playing(), false);
|
||||
}
|
||||
|
||||
TEST_F(AudioIngressTest, GetAudioFrameAfterRtpReceived) {
|
||||
rtc::Event event;
|
||||
auto handle_rtp = [&](const uint8_t* packet, size_t length, Unused) {
|
||||
ingress_->ReceivedRTPPacket(packet, length);
|
||||
event.Set();
|
||||
return true;
|
||||
};
|
||||
EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(handle_rtp));
|
||||
egress_->SendAudioData(GetAudioFrame(0));
|
||||
egress_->SendAudioData(GetAudioFrame(1));
|
||||
event.Wait(/*ms=*/1000);
|
||||
|
||||
AudioFrame audio_frame;
|
||||
EXPECT_EQ(
|
||||
ingress_->GetAudioFrameWithInfo(kPcmuFormat.clockrate_hz, &audio_frame),
|
||||
AudioMixer::Source::AudioFrameInfo::kNormal);
|
||||
EXPECT_FALSE(audio_frame.muted());
|
||||
EXPECT_EQ(audio_frame.num_channels_, 1u);
|
||||
EXPECT_EQ(audio_frame.samples_per_channel_,
|
||||
static_cast<size_t>(kPcmuFormat.clockrate_hz / 100));
|
||||
EXPECT_EQ(audio_frame.sample_rate_hz_, kPcmuFormat.clockrate_hz);
|
||||
EXPECT_NE(audio_frame.timestamp_, 0u);
|
||||
EXPECT_EQ(audio_frame.elapsed_time_ms_, 0);
|
||||
}
|
||||
|
||||
TEST_F(AudioIngressTest, GetSpeechOutputLevelFullRange) {
|
||||
// Per audio_level's kUpdateFrequency, we need 11 RTP to get audio level.
|
||||
constexpr int kNumRtp = 11;
|
||||
int rtp_count = 0;
|
||||
rtc::Event event;
|
||||
auto handle_rtp = [&](const uint8_t* packet, size_t length, Unused) {
|
||||
ingress_->ReceivedRTPPacket(packet, length);
|
||||
if (++rtp_count == kNumRtp) {
|
||||
event.Set();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(handle_rtp));
|
||||
for (int i = 0; i < kNumRtp * 2; i++) {
|
||||
egress_->SendAudioData(GetAudioFrame(i));
|
||||
fake_clock_.AdvanceTimeMilliseconds(10);
|
||||
}
|
||||
event.Wait(/*ms=*/1000);
|
||||
|
||||
for (int i = 0; i < kNumRtp; ++i) {
|
||||
AudioFrame audio_frame;
|
||||
EXPECT_EQ(
|
||||
ingress_->GetAudioFrameWithInfo(kPcmuFormat.clockrate_hz, &audio_frame),
|
||||
AudioMixer::Source::AudioFrameInfo::kNormal);
|
||||
}
|
||||
EXPECT_EQ(ingress_->GetSpeechOutputLevelFullRange(), kAudioLevel);
|
||||
}
|
||||
|
||||
TEST_F(AudioIngressTest, PreferredSampleRate) {
|
||||
rtc::Event event;
|
||||
auto handle_rtp = [&](const uint8_t* packet, size_t length, Unused) {
|
||||
ingress_->ReceivedRTPPacket(packet, length);
|
||||
event.Set();
|
||||
return true;
|
||||
};
|
||||
EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(handle_rtp));
|
||||
egress_->SendAudioData(GetAudioFrame(0));
|
||||
egress_->SendAudioData(GetAudioFrame(1));
|
||||
event.Wait(/*ms=*/1000);
|
||||
|
||||
AudioFrame audio_frame;
|
||||
EXPECT_EQ(
|
||||
ingress_->GetAudioFrameWithInfo(kPcmuFormat.clockrate_hz, &audio_frame),
|
||||
AudioMixer::Source::AudioFrameInfo::kNormal);
|
||||
EXPECT_EQ(ingress_->PreferredSampleRate(), kPcmuFormat.clockrate_hz);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace webrtc
|
||||
Reference in New Issue
Block a user