123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791 |
- // Copyright 2018 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "services/audio/snooper_node.h"
- #include <algorithm>
- #include <memory>
- #include <vector>
- #include "base/bind.h"
- #include "base/command_line.h"
- #include "base/files/file_path.h"
- #include "base/logging.h"
- #include "base/memory/scoped_refptr.h"
- #include "base/strings/string_piece.h"
- #include "base/test/test_mock_time_task_runner.h"
- #include "base/time/time.h"
- #include "media/base/audio_bus.h"
- #include "media/base/audio_parameters.h"
- #include "media/base/channel_layout.h"
- #include "services/audio/test/fake_consumer.h"
- #include "services/audio/test/fake_loopback_group_member.h"
- #include "testing/gtest/include/gtest/gtest.h"
- #include "third_party/abseil-cpp/absl/types/optional.h"
- namespace audio {
- namespace {
- // Used to test whether the output AudioBuses have had all their values set to
- // something finite.
- constexpr float kInvalidAudioSample = std::numeric_limits<float>::infinity();
- // The tones the source should generate into the left and right channels.
- constexpr double kLeftChannelFrequency = 500.0;
- constexpr double kRightChannelFrequency = 1200.0;
- constexpr double kSourceVolume = 0.5;
- // The duration of the audio that flows through the SnooperNode for each test.
- constexpr base::TimeDelta kTestDuration = base::Seconds(10);
- // The amount of time in the future where the inbound audio is being recorded.
- // This simulates an audio output stream that has rendered audio that is
- // scheduled to be played out in the near future.
- constexpr base::TimeDelta kInputAdvanceTime = base::Milliseconds(2);
- // Command-line switch to request dumping the recorded output to a WAV file for
- // analyzing the recorded output from one of the tests.
- constexpr base::StringPiece kDumpAsWavSwitch = "dump-as-wav";
- // Test parameters.
- struct InputAndOutputParams {
- media::AudioParameters input;
- media::AudioParameters output;
- };
- // Helper so that gtest can produce useful logging of the test parameters.
- std::ostream& operator<<(std::ostream& out,
- const InputAndOutputParams& test_params) {
- return out << "{input=" << test_params.input.AsHumanReadableString()
- << ", output=" << test_params.output.AsHumanReadableString()
- << "}";
- }
- class SnooperNodeTest : public testing::TestWithParam<InputAndOutputParams> {
- public:
- SnooperNodeTest() = default;
- ~SnooperNodeTest() override = default;
- const media::AudioParameters& input_params() const {
- return GetParam().input;
- }
- const media::AudioParameters& output_params() const {
- return GetParam().output;
- }
- base::TimeDelta output_delay() const { return output_delay_; }
- double max_relative_error() const { return max_relative_error_; }
- base::TestMockTimeTaskRunner* task_runner() const {
- return task_runner_.get();
- }
- FakeLoopbackGroupMember* group_member() { return &*group_member_; }
- SnooperNode* node() { return &*node_; }
- FakeConsumer* consumer() { return &*consumer_; }
- void SetUp() override {
- // Determine the amount of time in the past from which outbound audio should
- // be rendered. Use 20 ms as a reasonable baseline--the same as the initial
- // setting in audio::LoopbackStream--which will work for almost all normal
- // use cases.
- constexpr base::TimeDelta kBaselineOutputDelay = base::Milliseconds(20);
- output_delay_ = kBaselineOutputDelay;
- // Increase the output delay in special cases...
- if (input_params().sample_rate() < 32000) {
- // At the lower input sample rates (e.g., 8 kHz), prebufferring inside
- // SnooperNode's resampler becomes an issue: It uses a fixed size buffer
- // of 128 samples, which equates to a much longer duration of audio at the
- // lower sampling rates. With more buffering involved, the output delay
- // must be increased to avoid underruns.
- output_delay_ *= 2;
- } else if (input_params().GetBufferDuration() >
- output_params().GetBufferDuration()) {
- // For the HandlesBackwardsInput() test, the input goes backward by a full
- // buffer. If the duration of an input buffer is larger than an output
- // buffer, this could cause a brief moment of underrun (which is WAI!).
- // Rather than write lots of extra test code around such a specific
- // scenario, just fudge the delay up a little such that the underrun
- // cannot occur.
- output_delay_ += input_params().GetBufferDuration();
- }
- // Determine the maximum allowable error when measuring expected amplitudes.
- // This varies with the sampling rate because the loss of resolution at the
- // lower sampling rates can introduce error in the "amplitude-sensing
- // logic." Higher frequencies in the audio signal are especially vulnerable
- // to the error introduced by using low sampling rates.
- constexpr double kHighResAllowedError = 0.02; // 2% at 48 kHz
- constexpr double kHighResSampleRate = 48000;
- constexpr double kLowResAllowedError = 0.10; // 10% at 8 kHz
- constexpr double kLowResSampleRate = 8000;
- const int the_lower_sample_rate =
- std::min(input_params().sample_rate(), output_params().sample_rate());
- const double t = (the_lower_sample_rate - kHighResSampleRate) /
- (kLowResSampleRate - kHighResSampleRate);
- max_relative_error_ =
- kHighResAllowedError + t * (kLowResAllowedError - kHighResAllowedError);
- // Initialize a test clock and task runner. The starting TimeTicks value is
- // "huge" to ensure time calculations are being tested for overflow cases.
- task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>(
- base::Time(), base::TimeTicks() + base::Microseconds(INT64_C(1) << 62));
- }
- void TearDown() override {
- // If the "dump-as-wav" command-line switch is present, dump whatever has
- // been recorded in the consumer.
- const base::FilePath path =
- base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
- kDumpAsWavSwitch);
- if (!path.empty()) {
- if (consumer_) {
- consumer_->SaveToFile(path);
- } else {
- LOG(ERROR) << "No consumer: ignoring --" << kDumpAsWavSwitch;
- }
- }
- }
- // Selects which frequency to return for each channel based on the input and
- // output channel layout.
- enum WhichFlow : int8_t {
- FOR_INPUT,
- FOR_SWAPPED_INPUT,
- FOR_OUTPUT,
- FOR_SWAPPED_OUTPUT,
- };
- double GetLeftChannelFrequency(WhichFlow which) const {
- switch (which) {
- case FOR_INPUT:
- case FOR_OUTPUT:
- return kLeftChannelFrequency;
- case FOR_SWAPPED_INPUT:
- case FOR_SWAPPED_OUTPUT:
- return kRightChannelFrequency;
- }
- }
- double GetRightChannelFrequency(WhichFlow which) const {
- switch (which) {
- // If the test parameters call for stereo→mono channel down-mixing, use
- // the left channel frequency again for the right channel input. Down-
- // mixing is tested elsewhere.
- case FOR_INPUT:
- return (output_params().channels() == 1 ? kLeftChannelFrequency
- : kRightChannelFrequency);
- case FOR_SWAPPED_INPUT:
- return (output_params().channels() == 1 ? kRightChannelFrequency
- : kLeftChannelFrequency);
- // If the input was monaural, the output's right channel should contain
- // the input's "left" channel frequency.
- case FOR_OUTPUT:
- return (input_params().channels() == 1 ? kLeftChannelFrequency
- : kRightChannelFrequency);
- case FOR_SWAPPED_OUTPUT:
- return (input_params().channels() == 1 ? kRightChannelFrequency
- : kLeftChannelFrequency);
- }
- }
- void CreateNewPipeline() {
- group_member_.emplace(input_params());
- group_member_->SetChannelTone(0, GetLeftChannelFrequency(FOR_INPUT));
- if (input_params().channels() > 1) {
- group_member_->SetChannelTone(1, GetRightChannelFrequency(FOR_INPUT));
- }
- group_member_->SetVolume(kSourceVolume);
- node_.emplace(input_params(), output_params());
- group_member_->StartSnooping(node());
- consumer_.emplace(output_params().channels(),
- output_params().sample_rate());
- }
- // Have the SnooperNode render more output data and store it in the consumer
- // for later analysis.
- void RenderAndConsume(base::TimeTicks output_time) {
- // Assign invalid sample values to the AudioBus. Then, after the Render()
- // call, confirm that every sample was overwritten in the output AudioBus.
- const auto bus = media::AudioBus::Create(output_params());
- for (int ch = 0; ch < bus->channels(); ++ch) {
- std::fill_n(bus->channel(ch), bus->frames(), kInvalidAudioSample);
- }
- // If the SnooperNode provides a suggestion, check that |output_time| is
- // okay. Otherwise, Render() will be producing zero-fill gaps as the end of
- // |bus|. Don't do this check if there is already a test failure, and this
- // would just keep spamming the test output.
- if (!HasFailure()) {
- const absl::optional<base::TimeTicks> suggestion =
- node_->SuggestLatestRenderTime(bus->frames());
- if (suggestion) {
- EXPECT_LE(output_time, *suggestion)
- << "at frame=" << consumer_->GetRecordedFrameCount();
- }
- }
- node_->Render(output_time, bus.get());
- for (int ch = 0; ch < bus->channels(); ++ch) {
- EXPECT_FALSE(
- std::any_of(bus->channel(ch), bus->channel(ch) + bus->frames(),
- [](float x) { return x == kInvalidAudioSample; }))
- << " at output_time=" << output_time << ", ch=" << ch;
- }
- consumer_->Consume(*bus);
- }
- // Post delayed tasks to schedule normal, uninterrupted input with the default
- // kInputAdvanceTime delay.
- void ScheduleDefaultInputTasks(double skew = 1.0) {
- const base::TimeTicks start_time = task_runner_->NowTicks();
- const base::TimeTicks end_time = start_time + kTestDuration;
- const double time_step = skew / input_params().sample_rate();
- for (int position = 0;; position += input_params().frames_per_buffer()) {
- const base::TimeTicks task_time =
- start_time + base::Seconds(position * time_step);
- if (task_time >= end_time) {
- break;
- }
- const base::TimeTicks reference_time = task_time + kInputAdvanceTime;
- task_runner_->PostDelayedTask(
- FROM_HERE,
- base::BindOnce(&FakeLoopbackGroupMember::RenderMoreAudio,
- base::Unretained(group_member()), reference_time),
- task_time - start_time);
- }
- }
- // Post delayed tasks to schedule normal, uninterrupted output rendering to
- // occur at the default kCaptureDelay.
- void ScheduleDefaultRenderTasks(double skew = 1.0) {
- const base::TimeTicks start_time = task_runner_->NowTicks();
- const base::TimeTicks end_time = start_time + kTestDuration;
- const double time_step = skew / output_params().sample_rate();
- for (int position = 0;; position += output_params().frames_per_buffer()) {
- const base::TimeTicks task_time =
- start_time + base::Seconds(position * time_step);
- if (task_time >= end_time) {
- break;
- }
- const base::TimeTicks reference_time = task_time - output_delay();
- task_runner_->PostDelayedTask(
- FROM_HERE,
- base::BindOnce(&SnooperNodeTest::RenderAndConsume,
- base::Unretained(this), reference_time),
- task_time - start_time);
- }
- }
- void RunAllPendingTasks() { task_runner_->FastForwardUntilNoTasksRemain(); }
- private:
- scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
- // A suitable output delay to use for rendering audio from the pipeline. See
- // comments in SetUp() for further details.
- base::TimeDelta output_delay_;
- // The maximum allowable error relative to an expected amplitude.
- double max_relative_error_ = 0.0;
- // The pipeline from source to consumer.
- absl::optional<FakeLoopbackGroupMember> group_member_;
- absl::optional<SnooperNode> node_;
- absl::optional<FakeConsumer> consumer_;
- };
- // The skew test here is generating 10 seconds of audio per iteration, with
- // 5*5=25 iterations. That's 250 seconds of audio being generated to check for
- // skew-related issues. That's a lot of processing power needed! Thus, only
- // enable this test on optimized, non-debug builds, where it will run in a
- // reasonable amount of time. http://crbug.com/842428
- #ifdef NDEBUG
- #define MAYBE_ContinuousAudioFlowAdaptsToSkew ContinuousAudioFlowAdaptsToSkew
- #else
- #define MAYBE_ContinuousAudioFlowAdaptsToSkew \
- DISABLED_ContinuousAudioFlowAdaptsToSkew
- #endif
- // Tests that the internal time-stretching logic can handle various combinations
- // of input and output skews.
- TEST_P(SnooperNodeTest, MAYBE_ContinuousAudioFlowAdaptsToSkew) {
- // Note: A skew of 0.999 or 1.001 is very extreme. This is like saying the
- // clocks drift 1 ms for every second that goes by. If the implementation can
- // handle that, it's very likely to do a perfect job in-the-wild.
- for (double input_skew = 0.999; input_skew <= 1.001; input_skew += 0.0005) {
- for (double output_skew = 0.999; output_skew <= 1.001;
- output_skew += 0.0005) {
- SCOPED_TRACE(testing::Message() << "input_skew=" << input_skew
- << ", output_skew=" << output_skew);
- CreateNewPipeline();
- ScheduleDefaultInputTasks(input_skew);
- ScheduleDefaultRenderTasks(output_skew);
- RunAllPendingTasks();
- // All rendering for points-in-time before the audio from the source was
- // first recorded should be silence.
- const double expected_end_of_silence_position =
- ((input_skew * kInputAdvanceTime.InSecondsF()) +
- (output_skew * output_delay().InSecondsF())) *
- output_params().sample_rate();
- const double frames_in_one_millisecond =
- output_params().sample_rate() /
- double{base::Time::kMillisecondsPerSecond};
- EXPECT_NEAR(expected_end_of_silence_position,
- consumer()->FindEndOfSilence(0, 0),
- frames_in_one_millisecond);
- if (output_params().channels() > 1) {
- EXPECT_NEAR(expected_end_of_silence_position,
- consumer()->FindEndOfSilence(1, 0),
- frames_in_one_millisecond);
- }
- // Analyze the recording in several places for the expected tones.
- constexpr int kNumToneChecks = 16;
- for (int i = 1; i <= kNumToneChecks; ++i) {
- const int end_frame =
- consumer()->GetRecordedFrameCount() * i / kNumToneChecks;
- SCOPED_TRACE(testing::Message() << "end_frame=" << end_frame);
- EXPECT_NEAR(kSourceVolume,
- consumer()->ComputeAmplitudeAt(
- 0, GetLeftChannelFrequency(FOR_OUTPUT), end_frame),
- kSourceVolume * max_relative_error());
- if (output_params().channels() > 1) {
- EXPECT_NEAR(kSourceVolume,
- consumer()->ComputeAmplitudeAt(
- 1, GetRightChannelFrequency(FOR_OUTPUT), end_frame),
- kSourceVolume * max_relative_error());
- }
- }
- if (HasFailure()) {
- return;
- }
- }
- }
- }
- // Tests that gaps in the input are detected, are handled by introducing
- // zero-fill gaps in the output, and don't throw-off the timing/synchronization
- // between input and output.
- TEST_P(SnooperNodeTest, HandlesMissingInput) {
- CreateNewPipeline();
- // Schedule all input tasks, with drops to occur once per second for 1/4
- // second duration.
- const base::TimeTicks start_time = task_runner()->NowTicks();
- const base::TimeTicks end_time = start_time + kTestDuration;
- const double time_step = 1.0 / input_params().sample_rate();
- const int input_frames_in_one_second = input_params().sample_rate();
- // Drop duration: 1/4 second in terms of frames, aligned to frame buffer size.
- const int drop_duration =
- ((input_frames_in_one_second / 4) / input_params().frames_per_buffer()) *
- input_params().frames_per_buffer();
- int next_drop_position = input_frames_in_one_second;
- for (int position = 0;; position += input_params().frames_per_buffer()) {
- if (position >= next_drop_position) {
- position += drop_duration;
- next_drop_position += input_frames_in_one_second;
- }
- const base::TimeTicks task_time =
- start_time + base::Seconds(position * time_step);
- if (task_time >= end_time) {
- break;
- }
- const base::TimeTicks reference_time = task_time + kInputAdvanceTime;
- task_runner()->PostDelayedTask(
- FROM_HERE,
- base::BindOnce(&FakeLoopbackGroupMember::RenderMoreAudio,
- base::Unretained(group_member()), reference_time),
- task_time - start_time);
- }
- ScheduleDefaultRenderTasks();
- RunAllPendingTasks();
- // Check that there is silence in the drop positions, and that tones are
- // present around the silent sections. The ranges are adjusted to be 20 ms
- // away from the exact begin/end positions to account for a reasonable amount
- // of variance in due to the input buffer intervals.
- const int output_frames_in_one_second = output_params().sample_rate();
- const int output_frames_in_a_quarter_second = output_frames_in_one_second / 4;
- const int output_frames_in_20_milliseconds =
- output_frames_in_one_second * 20 / base::Time::kMillisecondsPerSecond;
- int output_silence_position =
- ((kInputAdvanceTime + output_delay()).InSecondsF() + 1.0) *
- output_params().sample_rate();
- for (int gap = 0; gap < 5; ++gap) {
- SCOPED_TRACE(testing::Message() << "gap=" << gap);
- // Just before the drop, there should be a tone.
- const int position_a_little_before_silence_begins =
- output_silence_position - output_frames_in_20_milliseconds;
- EXPECT_NEAR(
- kSourceVolume,
- consumer()->ComputeAmplitudeAt(0, GetLeftChannelFrequency(FOR_OUTPUT),
- position_a_little_before_silence_begins),
- kSourceVolume * max_relative_error());
- if (output_params().channels() > 1) {
- EXPECT_NEAR(kSourceVolume,
- consumer()->ComputeAmplitudeAt(
- 1, GetRightChannelFrequency(FOR_OUTPUT),
- position_a_little_before_silence_begins),
- kSourceVolume * max_relative_error());
- }
- // There should be silence during the drop.
- const int position_a_little_after_silence_begins =
- output_silence_position + output_frames_in_20_milliseconds;
- const int position_a_little_before_silence_ends =
- position_a_little_after_silence_begins +
- output_frames_in_a_quarter_second -
- 2 * output_frames_in_20_milliseconds;
- EXPECT_TRUE(
- consumer()->IsSilentInRange(0, position_a_little_after_silence_begins,
- position_a_little_before_silence_ends));
- // Finally, the tone should be back after the drop.
- const int position_a_little_after_silence_ends =
- position_a_little_before_silence_ends +
- 2 * output_frames_in_20_milliseconds;
- EXPECT_NEAR(
- kSourceVolume,
- consumer()->ComputeAmplitudeAt(0, GetLeftChannelFrequency(FOR_OUTPUT),
- position_a_little_after_silence_ends),
- kSourceVolume * max_relative_error());
- if (output_params().channels() > 1) {
- EXPECT_NEAR(kSourceVolume,
- consumer()->ComputeAmplitudeAt(
- 1, GetRightChannelFrequency(FOR_OUTPUT),
- position_a_little_after_silence_ends),
- kSourceVolume * max_relative_error());
- }
- output_silence_position += output_frames_in_one_second;
- }
- }
- // Tests that a backwards-jump in input reference timestamps doesn't attempt to
- // "re-write history" and otherwise maintains the timing/synchronization between
- // input and output. This is a regression test for http://crbug.com/934770.
- TEST_P(SnooperNodeTest, HandlesBackwardsInput) {
- CreateNewPipeline();
- // Schedule all input tasks. At the halfway point, simulate a device change
- // that shifts the timestamps backward by one buffer duration, and the
- // left/right sound tones are swapped.
- const base::TimeTicks start_time = task_runner()->NowTicks();
- const base::TimeTicks end_time = start_time + kTestDuration;
- const double time_step = 1.0 / input_params().sample_rate();
- const int change_position =
- input_params().sample_rate() * kTestDuration.InSeconds() / 2;
- int position_offset = 0;
- for (int position = 0;; position += input_params().frames_per_buffer()) {
- const base::TimeTicks task_time =
- start_time + base::Seconds(position * time_step);
- if (task_time >= end_time) {
- break;
- }
- if (position_offset == 0 && position >= change_position) {
- position_offset = -input_params().frames_per_buffer();
- task_runner()->PostDelayedTask(
- FROM_HERE,
- base::BindOnce(
- [](SnooperNodeTest* test) {
- test->group_member()->SetChannelTone(
- 0, test->GetLeftChannelFrequency(FOR_SWAPPED_INPUT));
- if (test->input_params().channels() > 1) {
- test->group_member()->SetChannelTone(
- 1, test->GetRightChannelFrequency(FOR_SWAPPED_INPUT));
- }
- },
- this),
- task_time - start_time);
- }
- const base::TimeTicks reference_time =
- start_time + kInputAdvanceTime +
- base::Seconds((position + position_offset) * time_step);
- task_runner()->PostDelayedTask(
- FROM_HERE,
- base::BindOnce(&FakeLoopbackGroupMember::RenderMoreAudio,
- base::Unretained(group_member()), reference_time),
- task_time - start_time);
- }
- ScheduleDefaultRenderTasks();
- RunAllPendingTasks();
- // In the consumer's recording, there should be audio having the default tones
- // before before the halfway point. After the halfway point, the tones should
- // be swapped (left vs right). Sample once every second, starting at a
- // ~half-second offset.
- const int output_position_halfway =
- (kInputAdvanceTime + output_delay() + (kTestDuration / 2)).InSecondsF() *
- output_params().sample_rate();
- const int output_frames_in_one_second = output_params().sample_rate();
- int output_position =
- ((kInputAdvanceTime + output_delay()).InSecondsF() + 0.5) *
- output_params().sample_rate();
- for (int output_end = consumer()->GetRecordedFrameCount();
- output_position < output_end;
- output_position += output_frames_in_one_second) {
- const int left_ch_freq = (output_position < output_position_halfway)
- ? GetLeftChannelFrequency(FOR_OUTPUT)
- : GetLeftChannelFrequency(FOR_SWAPPED_OUTPUT);
- EXPECT_NEAR(
- kSourceVolume,
- consumer()->ComputeAmplitudeAt(0, left_ch_freq, output_position),
- kSourceVolume * max_relative_error());
- if (output_params().channels() > 1) {
- const int right_ch_freq =
- (output_position < output_position_halfway)
- ? GetRightChannelFrequency(FOR_OUTPUT)
- : GetRightChannelFrequency(FOR_SWAPPED_OUTPUT);
- EXPECT_NEAR(
- kSourceVolume,
- consumer()->ComputeAmplitudeAt(1, right_ch_freq, output_position),
- kSourceVolume * max_relative_error());
- }
- }
- }
- // Tests that reasonable render times are suggested as audio is feeding into, or
- // not feeding into, the SnooperNode.
- TEST_P(SnooperNodeTest, SuggestsRenderTimes) {
- constexpr base::TimeDelta kTwentyMilliseconds = base::Milliseconds(20);
- CreateNewPipeline();
- // Before any audio has flowed into the SnooperNode, there should be nothing
- // to base a suggestion on.
- EXPECT_FALSE(
- node()->SuggestLatestRenderTime(output_params().frames_per_buffer()));
- // Feed-in the first buffer and expect a render time suggestion that is
- // greater than 150% the output buffer's duration amount of time in the
- // past. (The extra 50% is a safety margin; see internal code comments for
- // further details.) The suggestion should also not be too far in the past.
- const base::TimeTicks first_input_time = task_runner()->NowTicks();
- group_member()->RenderMoreAudio(first_input_time);
- const absl::optional<base::TimeTicks> first_suggestion =
- node()->SuggestLatestRenderTime(output_params().frames_per_buffer());
- ASSERT_TRUE(first_suggestion);
- base::TimeTicks time_at_end_of_input =
- first_input_time + input_params().GetBufferDuration();
- const base::TimeDelta required_duration_buffered =
- output_params().GetBufferDuration() * 3 / 2;
- EXPECT_GT(time_at_end_of_input - required_duration_buffered,
- *first_suggestion);
- EXPECT_LT(
- time_at_end_of_input - required_duration_buffered - kTwentyMilliseconds,
- *first_suggestion);
- // If another suggestion is solicited before more input was provided,
- // SnooperNode shouldn't give one.
- for (int i = 0; i < 3; ++i) {
- EXPECT_FALSE(
- node()->SuggestLatestRenderTime(output_params().frames_per_buffer()));
- }
- // When feeding-in successive buffers, a new suggestion can be given after
- // each, reflecting the timing of the additional audio that has been buffered.
- for (int i = 1; i <= 3; ++i) {
- const base::TimeTicks next_input_time =
- first_input_time +
- base::Seconds(i * input_params().frames_per_buffer() /
- static_cast<double>(input_params().sample_rate()));
- group_member()->RenderMoreAudio(next_input_time);
- const absl::optional<base::TimeTicks> next_suggestion =
- node()->SuggestLatestRenderTime(output_params().frames_per_buffer());
- ASSERT_TRUE(next_suggestion);
- time_at_end_of_input = next_input_time + input_params().GetBufferDuration();
- EXPECT_GT(time_at_end_of_input - required_duration_buffered,
- *next_suggestion);
- EXPECT_LT(
- time_at_end_of_input - required_duration_buffered - kTwentyMilliseconds,
- *next_suggestion);
- }
- }
- namespace {
- // Used in the HandlesSeekedRenderTimes test below. Returns one of 10 possible
- // tone frequencies to use at the specified time |offset| in the audio.
- double MapTimeOffsetToATone(base::TimeDelta offset) {
- constexpr double kMinFrequency = 200;
- constexpr double kMaxFrequency = 2000;
- constexpr int kNumToneSteps = 10;
- const int64_t step_number = offset.IntDiv(kTestDuration / kNumToneSteps);
- const double t = static_cast<double>(step_number) / kNumToneSteps;
- return kMinFrequency + t * (kMaxFrequency - kMinFrequency);
- }
- } // namespace
- // Tests that the SnooperNode can be asked to seek (forward or backward) its
- // Render() positions, as the needs of the system demand.
- TEST_P(SnooperNodeTest, HandlesSeekedRenderTimes) {
- constexpr base::TimeDelta kQuarterSecond = base::Milliseconds(250);
- CreateNewPipeline();
- // Schedule input tasks where the audio tones are changed once per second, to
- // allow for identifying the timing of the audio in the consumer's recording
- // later on.
- const base::TimeTicks start_time = task_runner()->NowTicks();
- const base::TimeTicks end_time = start_time + kTestDuration;
- double time_step = 1.0 / input_params().sample_rate();
- for (int position = 0;; position += input_params().frames_per_buffer()) {
- const base::TimeTicks task_time =
- start_time + base::Seconds(position * time_step);
- if (task_time >= end_time) {
- break;
- }
- const base::TimeTicks reference_time = task_time + kInputAdvanceTime;
- task_runner()->PostDelayedTask(
- FROM_HERE,
- base::BindOnce(
- [](FakeLoopbackGroupMember* group_member,
- base::TimeTicks start_time, base::TimeTicks reference_time) {
- group_member->SetChannelTone(
- FakeLoopbackGroupMember::kSetAllChannels,
- MapTimeOffsetToATone(reference_time - start_time));
- group_member->RenderMoreAudio(reference_time);
- },
- group_member(), start_time, reference_time),
- task_time - start_time);
- }
- // Schedule normal render tasks for the first third of the test, then skip
- // back a quarter-second and run for another third of the test, then skip
- // forward a quarter-second and run to the end.
- time_step = 1.0 / output_params().sample_rate();
- for (int position = 0;; position += output_params().frames_per_buffer()) {
- const base::TimeTicks task_time =
- start_time + base::Seconds(position * time_step);
- if (task_time >= end_time) {
- break;
- }
- base::TimeDelta time_offset = task_time - start_time;
- if (time_offset < (kTestDuration / 3) ||
- time_offset >= (kTestDuration * 2 / 3)) {
- time_offset = base::TimeDelta();
- } else {
- time_offset = -kQuarterSecond;
- }
- const base::TimeTicks reference_time =
- task_time + time_offset - output_delay();
- task_runner()->PostDelayedTask(
- FROM_HERE,
- base::BindOnce(&SnooperNodeTest::RenderAndConsume,
- base::Unretained(this), reference_time),
- task_time - start_time);
- }
- RunAllPendingTasks();
- // Examine the consumer's recorded audio for the expected audio signal: For
- // the first third of the test, the consumer should hear the first few tones.
- // Then, at the point where rendering seeked backward, there will be a
- // zero-fill gap for a quarter second, followed by the tone changes being
- // "late" by a quarter second. Finally, at the point where rendering seeked
- // forward, the tone changes will be shifted back again.
- const base::TimeDelta lead_in = kInputAdvanceTime + output_delay();
- for (base::TimeDelta recording_time = lead_in + kQuarterSecond;
- recording_time < kTestDuration; recording_time += kQuarterSecond) {
- const base::TimeDelta render_time = recording_time - kInputAdvanceTime;
- base::TimeDelta input_time =
- recording_time - lead_in - input_params().GetBufferDuration();
- // The recording is shifted forward during the middle-third of the test.
- if (render_time >= (kTestDuration / 3) &&
- render_time < (kTestDuration * 2 / 3)) {
- input_time -= kQuarterSecond;
- }
- const double expected_freq = MapTimeOffsetToATone(input_time);
- SCOPED_TRACE(testing::Message() << "recording_time=" << recording_time
- << ", expected_freq=" << expected_freq);
- const int position =
- output_params().sample_rate() * recording_time.InSecondsF() -
- output_params().frames_per_buffer();
- if (render_time >= (kTestDuration / 3) &&
- render_time < (kTestDuration / 3 + kQuarterSecond)) {
- // Special case: Expect the zero-fill gap immediately after the first
- // discontinuity.
- for (int ch = 0; ch < output_params().channels(); ++ch) {
- EXPECT_TRUE(consumer()->IsSilentInRange(
- ch, position - output_params().frames_per_buffer(), position));
- }
- } else {
- for (int ch = 0; ch < output_params().channels(); ++ch) {
- EXPECT_NEAR(kSourceVolume,
- consumer()->ComputeAmplitudeAt(ch, expected_freq, position),
- kSourceVolume * max_relative_error());
- }
- }
- }
- }
- InputAndOutputParams MakeParams(media::ChannelLayout input_channel_layout,
- int input_sample_rate,
- int input_frames_per_buffer,
- media::ChannelLayout output_channel_layout,
- int output_sample_rate,
- int output_frames_per_buffer) {
- return InputAndOutputParams{
- media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
- input_channel_layout, input_sample_rate,
- input_frames_per_buffer),
- media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
- output_channel_layout, output_sample_rate,
- output_frames_per_buffer)};
- }
- INSTANTIATE_TEST_SUITE_P(
- All,
- SnooperNodeTest,
- testing::Values(MakeParams(media::CHANNEL_LAYOUT_STEREO,
- 48000,
- 480,
- media::CHANNEL_LAYOUT_STEREO,
- 48000,
- 480),
- MakeParams(media::CHANNEL_LAYOUT_STEREO,
- 48000,
- 64,
- media::CHANNEL_LAYOUT_STEREO,
- 48000,
- 480),
- MakeParams(media::CHANNEL_LAYOUT_STEREO,
- 44100,
- 64,
- media::CHANNEL_LAYOUT_STEREO,
- 48000,
- 480),
- MakeParams(media::CHANNEL_LAYOUT_STEREO,
- 48000,
- 512,
- media::CHANNEL_LAYOUT_STEREO,
- 44100,
- 441),
- MakeParams(media::CHANNEL_LAYOUT_MONO,
- 8000,
- 64,
- media::CHANNEL_LAYOUT_STEREO,
- 48000,
- 480),
- MakeParams(media::CHANNEL_LAYOUT_STEREO,
- 48000,
- 480,
- media::CHANNEL_LAYOUT_MONO,
- 8000,
- 80)));
- } // namespace
- } // namespace audio
|