web_engine_audio_output_device_test.cc 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "fuchsia_web/webengine/renderer/web_engine_audio_output_device.h"
  5. #include "base/logging.h"
  6. #include "base/memory/ref_counted.h"
  7. #include "base/test/task_environment.h"
  8. #include "base/threading/thread.h"
  9. #include "base/threading/thread_task_runner_handle.h"
  10. #include "base/time/time.h"
  11. #include "media/base/audio_renderer_sink.h"
  12. #include "media/base/audio_timestamp_helper.h"
  13. #include "media/fuchsia/audio/fake_audio_consumer.h"
  14. #include "testing/gtest/include/gtest/gtest.h"
  15. namespace {
  16. constexpr int kSampleRate = 44100;
  17. constexpr media::ChannelLayout kChannelLayout = media::CHANNEL_LAYOUT_STEREO;
  18. constexpr int kNumChannels = 2;
  19. constexpr uint64_t kTestSessionId = 42;
  20. constexpr base::TimeDelta kPeriod = base::Milliseconds(10);
  21. constexpr int kFramesPerPeriod = 441;
  22. } // namespace
  23. class TestRenderer : public media::AudioRendererSink::RenderCallback {
  24. public:
  25. TestRenderer() = default;
  26. ~TestRenderer() override = default;
  27. // AudioRendererSink::Renderer interface.
  28. int Render(base::TimeDelta delay,
  29. base::TimeTicks delay_timestamp,
  30. int prior_frames_skipped,
  31. media::AudioBus* dest) override {
  32. EXPECT_EQ(dest->channels(), kNumChannels);
  33. frames_skipped_ += prior_frames_skipped;
  34. frames_rendered_ += dest->frames();
  35. EXPECT_GT(delay, base::TimeDelta());
  36. auto presentation_time = delay_timestamp + delay;
  37. EXPECT_GT(presentation_time, last_presentation_time_);
  38. last_presentation_time_ = presentation_time;
  39. return dest->frames();
  40. }
  41. void OnRenderError() override { num_render_errors_++; }
  42. int frames_rendered() const { return frames_rendered_; }
  43. void reset_frames_rendered() { frames_rendered_ = 0; }
  44. int frames_skipped() const { return frames_skipped_; }
  45. int num_render_errors() const { return num_render_errors_; }
  46. base::TimeTicks last_presentation_time() const {
  47. return last_presentation_time_;
  48. }
  49. private:
  50. int frames_rendered_ = 0;
  51. int frames_skipped_ = 0;
  52. int num_render_errors_ = 0;
  53. base::TimeTicks last_presentation_time_;
  54. };
  55. class WebEngineAudioOutputDeviceTest : public testing::Test {
  56. public:
  57. WebEngineAudioOutputDeviceTest() {
  58. fidl::InterfaceHandle<fuchsia::media::AudioConsumer> audio_consumer;
  59. fake_audio_consumer_ = std::make_unique<media::FakeAudioConsumer>(
  60. kTestSessionId, audio_consumer.NewRequest());
  61. output_device_ = WebEngineAudioOutputDevice::Create(
  62. std::move(audio_consumer), base::ThreadTaskRunnerHandle::Get());
  63. }
  64. ~WebEngineAudioOutputDeviceTest() override {
  65. // Stop() must be called before destruction to release resources.
  66. output_device_->Stop();
  67. // WebEngineAudioOutputDevice::Stop() posts a task to run
  68. // StopOnAudioThread() on `task_runner_`. RunUntilIdle() ensures the request
  69. // to stop is fulfilled.
  70. task_environment_.RunUntilIdle();
  71. }
  72. protected:
  73. void Initialize() {
  74. output_device_->Initialize(
  75. media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
  76. kChannelLayout, kSampleRate, kFramesPerPeriod),
  77. &renderer_);
  78. task_environment_.RunUntilIdle();
  79. EXPECT_EQ(renderer_.frames_rendered(), 0);
  80. }
  81. void InitializeAndStart() {
  82. Initialize();
  83. // As soon as Start() is processed WebEngineAudioOutputDevice is expected to
  84. // start rendering some samples.
  85. output_device_->Start();
  86. task_environment_.RunUntilIdle();
  87. EXPECT_GT(renderer_.frames_rendered(), 0);
  88. }
  89. void CallPumpSamples() {
  90. output_device_->PumpSamples(base::TimeTicks::Now() +
  91. base::Milliseconds(200));
  92. }
  93. void ValidatePresentationTime() {
  94. // Verify that the current renderer lead time is in the
  95. // [min_lead_time, min_lead_time + 30ms] range. 30ms is chosen to allow
  96. // WebEngineAudioOutputDevice to pre-render slightely ahead of the target
  97. // time, while keeping latency reasonably low.
  98. auto lead_time =
  99. renderer_.last_presentation_time() - base::TimeTicks::Now();
  100. EXPECT_GT(lead_time, media::FakeAudioConsumer::kMinLeadTime);
  101. EXPECT_LT(lead_time,
  102. media::FakeAudioConsumer::kMinLeadTime + base::Milliseconds(30));
  103. }
  104. base::test::SingleThreadTaskEnvironment task_environment_{
  105. base::test::SingleThreadTaskEnvironment::MainThreadType::IO,
  106. base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  107. std::unique_ptr<media::FakeAudioConsumer> fake_audio_consumer_;
  108. TestRenderer renderer_;
  109. scoped_refptr<WebEngineAudioOutputDevice> output_device_;
  110. };
  111. TEST_F(WebEngineAudioOutputDeviceTest, Start) {
  112. Initialize();
  113. // Verify that playback doesn't start before Start().
  114. task_environment_.FastForwardBy(base::Seconds(2));
  115. EXPECT_EQ(renderer_.frames_rendered(), 0);
  116. // Rendering should start after Start().
  117. output_device_->Start();
  118. task_environment_.RunUntilIdle();
  119. EXPECT_GT(renderer_.frames_rendered(), 0);
  120. ValidatePresentationTime();
  121. }
  122. TEST_F(WebEngineAudioOutputDeviceTest, StartAndPlay) {
  123. InitializeAndStart();
  124. renderer_.reset_frames_rendered();
  125. // Try advancing time and verify that WebEngineAudioOutputDevice keeps calling
  126. // Render().
  127. for (int i = 0; i < 3; ++i) {
  128. task_environment_.FastForwardBy(kPeriod);
  129. EXPECT_EQ(renderer_.frames_rendered(), kFramesPerPeriod);
  130. EXPECT_EQ(renderer_.frames_skipped(), 0);
  131. renderer_.reset_frames_rendered();
  132. }
  133. }
  134. TEST_F(WebEngineAudioOutputDeviceTest, Pause) {
  135. InitializeAndStart();
  136. renderer_.reset_frames_rendered();
  137. // Advancing time and verify that WebEngineAudioOutputDevice keeps calling
  138. // Render().
  139. task_environment_.FastForwardBy(kPeriod);
  140. EXPECT_EQ(renderer_.frames_rendered(), kFramesPerPeriod);
  141. EXPECT_EQ(renderer_.frames_skipped(), 0);
  142. renderer_.reset_frames_rendered();
  143. // Render() should not be called while paused.
  144. output_device_->Pause();
  145. task_environment_.FastForwardBy(base::Seconds(10));
  146. EXPECT_EQ(renderer_.frames_rendered(), 0);
  147. // Unpause the stream and verify that Render() is being called now.
  148. output_device_->Play();
  149. task_environment_.FastForwardBy(kPeriod);
  150. EXPECT_GT(renderer_.frames_rendered(), 0);
  151. EXPECT_EQ(renderer_.frames_skipped(), 0);
  152. }
  153. TEST_F(WebEngineAudioOutputDeviceTest, Underflow) {
  154. InitializeAndStart();
  155. renderer_.reset_frames_rendered();
  156. // Missing the timer once should not cause any issues. Timer tasks can't
  157. // always run at the exact scheduled time. WebEngineAudioOutputDevice should
  158. // be resilient to small delays.
  159. task_environment_.AdvanceClock(kPeriod * 2);
  160. task_environment_.RunUntilIdle();
  161. EXPECT_EQ(renderer_.frames_rendered(), kFramesPerPeriod * 2);
  162. EXPECT_EQ(renderer_.frames_skipped(), 0);
  163. renderer_.reset_frames_rendered();
  164. // Advance time by 100ms, causing some frames to be skipped.
  165. task_environment_.AdvanceClock(kPeriod * 10);
  166. task_environment_.RunUntilIdle();
  167. EXPECT_EQ(renderer_.frames_rendered(), kFramesPerPeriod * 3);
  168. EXPECT_EQ(renderer_.frames_skipped(), kFramesPerPeriod * 7);
  169. renderer_.reset_frames_rendered();
  170. ValidatePresentationTime();
  171. }
  172. TEST_F(WebEngineAudioOutputDeviceTest, Error) {
  173. InitializeAndStart();
  174. renderer_.reset_frames_rendered();
  175. fake_audio_consumer_.reset();
  176. task_environment_.RunUntilIdle();
  177. EXPECT_EQ(renderer_.num_render_errors(), 1);
  178. EXPECT_EQ(renderer_.frames_rendered(), 0);
  179. }
  180. TEST_F(WebEngineAudioOutputDeviceTest, Stop) {
  181. InitializeAndStart();
  182. renderer_.reset_frames_rendered();
  183. // Call Stop() and then PumpSamples() immediately after that. The callback
  184. // should not be called.
  185. output_device_->Stop();
  186. CallPumpSamples();
  187. EXPECT_EQ(renderer_.frames_rendered(), 0);
  188. }