text: // Copyright (c) 2012 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 "media/audio/audio_output_device.h"
  
  #include <stdint.h>
  
  #include <utility>
  #include <vector>
  
  #include "base/bind.h"
  #include "base/callback.h"
  #include "base/callback_helpers.h"
  #include "base/memory/ptr_util.h"
  #include "base/memory/raw_ptr.h"
  #include "base/memory/shared_memory_mapping.h"
  #include "base/memory/unsafe_shared_memory_region.h"
  #include "base/sync_socket.h"
  #include "base/task/single_thread_task_runner.h"
  #include "base/task/task_runner.h"
  #include "base/test/task_environment.h"
  #include "base/threading/thread_task_runner_handle.h"
  #include "build/build_config.h"
  #include "testing/gmock/include/gmock/gmock.h"
  #include "testing/gtest/include/gtest/gtest.h"
  
  using base::CancelableSyncSocket;
  using base::UnsafeSharedMemoryRegion;
  using base::WritableSharedMemoryMapping;
  using base::SyncSocket;
  using testing::_;
  using testing::DoAll;
  using testing::Invoke;
  using testing::Return;
  using testing::WithArg;
  using testing::StrictMock;
  using testing::NiceMock;
  using testing::NotNull;
  using testing::Mock;
  
  namespace media {
  
  namespace {
  
  constexpr char kDefaultDeviceId[] = "";
  constexpr char kNonDefaultDeviceId[] = "valid-nondefault-device-id";
  constexpr char kUnauthorizedDeviceId[] = "unauthorized-device-id";
  constexpr base::TimeDelta kAuthTimeout = base::Milliseconds(10000);
  
  class MockRenderCallback : public AudioRendererSink::RenderCallback {
   public:
    MockRenderCallback() = default;
    ~MockRenderCallback() override = default;
  
    MOCK_METHOD4(Render,
                 int(base::TimeDelta delay,
                     base::TimeTicks timestamp,
                     int prior_frames_skipped,
                     AudioBus* dest));
    MOCK_METHOD0(OnRenderError, void());
  };
  
  class MockAudioOutputIPC : public AudioOutputIPC {
   public:
    MockAudioOutputIPC() = default;
    ~MockAudioOutputIPC() override = default;
  
    MOCK_METHOD3(RequestDeviceAuthorization,
                 void(AudioOutputIPCDelegate* delegate,
                      const base::UnguessableToken& session_id,
                      const std::string& device_id));
    MOCK_METHOD3(
        CreateStream,
        void(AudioOutputIPCDelegate* delegate,
             const AudioParameters& params,
             const absl::optional<base::UnguessableToken>& processing_id));
    MOCK_METHOD0(PlayStream, void());
    MOCK_METHOD0(PauseStream, void());
    MOCK_METHOD0(FlushStream, void());
    MOCK_METHOD0(CloseStream, void());
    MOCK_METHOD1(SetVolume, void(double volume));
  };
  
  }  // namespace.
  
  class AudioOutputDeviceTest : public testing::Test {
   public:
    AudioOutputDeviceTest();
  
    AudioOutputDeviceTest(const AudioOutputDeviceTest&) = delete;
    AudioOutputDeviceTest& operator=(const AudioOutputDeviceTest&) = delete;
  
    ~AudioOutputDeviceTest() override;
  
    void ReceiveAuthorization(OutputDeviceStatus device_status);
    void StartAudioDevice();
    void CallOnStreamCreated();
    void StopAudioDevice();
    void FlushAudioDevice();
    void CreateDevice(const std::string& device_id,
                      base::TimeDelta timeout = kAuthTimeout);
    void SetDevice(const std::string& device_id);
  
    MOCK_METHOD1(OnDeviceInfoReceived, void(OutputDeviceInfo));
  
   protected:
    base::test::TaskEnvironment task_env_{
        base::test::TaskEnvironment::TimeSource::MOCK_TIME};
    AudioParameters default_audio_parameters_;
    StrictMock<MockRenderCallback> callback_;
    raw_ptr<MockAudioOutputIPC> audio_output_ipc_;  // owned by audio_device_
    scoped_refptr<AudioOutputDevice> audio_device_;
    OutputDeviceStatus device_status_;
  
   private:
    int CalculateMemorySize();
  
    UnsafeSharedMemoryRegion shared_memory_region_;
    WritableSharedMemoryMapping shared_memory_mapping_;
    CancelableSyncSocket browser_socket_;
    CancelableSyncSocket renderer_socket_;
  };
  
  AudioOutputDeviceTest::AudioOutputDeviceTest()
      : device_status_(OUTPUT_DEVICE_STATUS_ERROR_INTERNAL) {
    default_audio_parameters_.Reset(AudioParameters::AUDIO_PCM_LINEAR,
                                    CHANNEL_LAYOUT_STEREO, 48000, 1024);
    SetDevice(kDefaultDeviceId);
  }
  
  AudioOutputDeviceTest::~AudioOutputDeviceTest() {
    audio_device_ = nullptr;
  }
  
  void AudioOutputDeviceTest::CreateDevice(const std::string& device_id,
                                           base::TimeDelta timeout) {
    // Make sure the previous device is properly cleaned up.
    if (audio_device_)
      StopAudioDevice();
  
    audio_output_ipc_ = new NiceMock<MockAudioOutputIPC>();
    audio_device_ = new AudioOutputDevice(
        base::WrapUnique(audio_output_ipc_.get()),
        task_env_.GetMainThreadTaskRunner(),
        AudioSinkParameters(base::UnguessableToken(), device_id), timeout);
  }
  
  void AudioOutputDeviceTest::SetDevice(const std::string& device_id) {
    CreateDevice(device_id);
    EXPECT_CALL(*audio_output_ipc_,
                RequestDeviceAuthorization(audio_device_.get(),
                                           base::UnguessableToken(), device_id));
    audio_device_->RequestDeviceAuthorization();
    task_env_.FastForwardBy(base::TimeDelta());
  
    // Simulate response from browser
    OutputDeviceStatus device_status =
        (device_id == kUnauthorizedDeviceId)
            ? OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED
            : OUTPUT_DEVICE_STATUS_OK;
    ReceiveAuthorization(device_status);
  
    audio_device_->Initialize(default_audio_parameters_,
                              &callback_);
  }
  
  void AudioOutputDeviceTest::ReceiveAuthorization(OutputDeviceStatus status) {
    device_status_ = status;
    if (device_status_ != OUTPUT_DEVICE_STATUS_OK)
      EXPECT_CALL(*audio_output_ipc_, CloseStream());
  
    audio_device_->OnDeviceAuthorized(device_status_, default_audio_parameters_,
                                      kDefaultDeviceId);
    task_env_.FastForwardBy(base::TimeDelta());
  }
  
  void AudioOutputDeviceTest::StartAudioDevice() {
    if (device_status_ == OUTPUT_DEVICE_STATUS_OK)
      EXPECT_CALL(*audio_output_ipc_, CreateStream(audio_device_.get(), _, _));
    else
      EXPECT_CALL(callback_, OnRenderError());
  
    audio_device_->Start();
    task_env_.FastForwardBy(base::TimeDelta());
  }
  
  void AudioOutputDeviceTest::CallOnStreamCreated() {
    const uint32_t kMemorySize =
        ComputeAudioOutputBufferSize(default_audio_parameters_);
  
    shared_memory_region_ = base::UnsafeSharedMemoryRegion::Create(kMemorySize);
    ASSERT_TRUE(shared_memory_region_.IsValid());
    shared_memory_mapping_ = shared_memory_region_.Map();
    ASSERT_TRUE(shared_memory_mapping_.IsValid());
    memset(shared_memory_mapping_.memory(), 0xff, kMemorySize);
  
    ASSERT_TRUE(CancelableSyncSocket::CreatePair(&browser_socket_,
                                                 &renderer_socket_));
  
    // Create duplicates of the handles we pass to AudioOutputDevice since
    // ownership will be transferred and AudioOutputDevice is responsible for
    // freeing.
    base::UnsafeSharedMemoryRegion duplicated_memory_region =
        shared_memory_region_.Duplicate();
    ASSERT_TRUE(duplicated_memory_region.IsValid());
  
    audio_device_->OnStreamCreated(std::move(duplicated_memory_region),
                                   renderer_socket_.Take(),
                                   /*playing_automatically*/ false);
    task_env_.FastForwardBy(base::TimeDelta());
  }
  
  void AudioOutputDeviceTest::StopAudioDevice() {
    if (device_status_ == OUTPUT_DEVICE_STATUS_OK)
      EXPECT_CALL(*audio_output_ipc_, CloseStream());
  
    audio_device_->Stop();
    task_env_.FastForwardBy(base::TimeDelta());
  }
  
  void AudioOutputDeviceTest::FlushAudioDevice() {
    if (device_status_ == OUTPUT_DEVICE_STATUS_OK)
      EXPECT_CALL(*audio_output_ipc_, FlushStream());
  
    audio_device_->Flush();
    task_env_.FastForwardBy(base::TimeDelta());
  }
  
  TEST_F(AudioOutputDeviceTest, Initialize) {
    // Tests that the object can be constructed, initialized and destructed
    // without having ever been started.
    StopAudioDevice();
  }
  
  // Calls Start() followed by an immediate Stop() and check for the basic message
  // filter messages being sent in that case.
  TEST_F(AudioOutputDeviceTest, StartStop) {
    StartAudioDevice();
    StopAudioDevice();
  }
  
  // AudioOutputDevice supports multiple start/stop sequences.
  TEST_F(AudioOutputDeviceTest, StartStopStartStop) {
    StartAudioDevice();
    StopAudioDevice();
    StartAudioDevice();
    StopAudioDevice();
  }
  
  // Simulate receiving OnStreamCreated() prior to processing ShutDownOnIOThread()
  // on the IO loop.
  TEST_F(AudioOutputDeviceTest, StopBeforeRender) {
    StartAudioDevice();
  
    // Call Stop() but don't run the IO loop yet.
    audio_device_->Stop();
  
    // Expect us to shutdown IPC but not to render anything despite the stream
    // getting created.
    EXPECT_CALL(*audio_output_ipc_, CloseStream());
    CallOnStreamCreated();
  }
  
  // Multiple start/stop with nondefault device
  TEST_F(AudioOutputDeviceTest, NonDefaultStartStopStartStop) {
    SetDevice(kNonDefaultDeviceId);
    StartAudioDevice();
    StopAudioDevice();
  
    EXPECT_CALL(*audio_output_ipc_,
                RequestDeviceAuthorization(audio_device_.get(),
                                           base::UnguessableToken(), _));
    StartAudioDevice();
    // Simulate reply from browser
    ReceiveAuthorization(OUTPUT_DEVICE_STATUS_OK);
  
    StopAudioDevice();
  }
  
  TEST_F(AudioOutputDeviceTest, UnauthorizedDevice) {
    SetDevice(kUnauthorizedDeviceId);
    StartAudioDevice();
    StopAudioDevice();
  }
  
  TEST_F(AudioOutputDeviceTest,
         StartUnauthorizedDeviceAndStopBeforeErrorFires_NoError) {
    SetDevice(kUnauthorizedDeviceId);
    audio_device_->Start();
    // Don't run the runloop. We stop before |audio_device| gets the
    // authorization error, so it's not allowed to dereference |callback_|.
    EXPECT_CALL(callback_, OnRenderError()).Times(0);
    StopAudioDevice();
  }
  
  TEST_F(AudioOutputDeviceTest, AuthorizationFailsBeforeInitialize_NoError) {
    // Clear audio device set by fixture.
    StopAudioDevice();
    audio_output_ipc_ = new NiceMock<MockAudioOutputIPC>();
    audio_device_ = new AudioOutputDevice(
        base::WrapUnique(audio_output_ipc_.get()),
        task_env_.GetMainThreadTaskRunner(),
        AudioSinkParameters(base::UnguessableToken(), kDefaultDeviceId),
        kAuthTimeout);
    EXPECT_CALL(
        *audio_output_ipc_,
        RequestDeviceAuthorization(audio_device_.get(), base::UnguessableToken(),
                                   kDefaultDeviceId));
  
    audio_device_->RequestDeviceAuthorization();
    audio_device_->Initialize(default_audio_parameters_, &callback_);
    task_env_.FastForwardBy(base::TimeDelta());
    audio_device_->Stop();
  
    // We've stopped, so accessing |callback_| isn't ok.
    EXPECT_CALL(callback_, OnRenderError()).Times(0);
    audio_device_->OnDeviceAuthorized(OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED,
                                      default_audio_parameters_,
                                      kDefaultDeviceId);
    task_env_.FastForwardBy(base::TimeDelta());
  }
  
  TEST_F(AudioOutputDeviceTest, AuthorizationTimedOut) {
    CreateDevice(kNonDefaultDeviceId);
    EXPECT_CALL(
        *audio_output_ipc_,
        RequestDeviceAuthorization(audio_device_.get(), base::UnguessableToken(),
                                   kNonDefaultDeviceId));
    EXPECT_CALL(*audio_output_ipc_, CloseStream());
  
    // Request authorization; no reply from the browser.
    audio_device_->RequestDeviceAuthorization();
  
    // Advance time until we hit the timeout.
    task_env_.FastForwardUntilNoTasksRemain();
  
    audio_device_->Stop();
    task_env_.FastForwardBy(base::TimeDelta());
  }
  
  TEST_F(AudioOutputDeviceTest, GetOutputDeviceInfoAsync_Error) {
    CreateDevice(kUnauthorizedDeviceId, base::TimeDelta());
    EXPECT_CALL(
        *audio_output_ipc_,
        RequestDeviceAuthorization(audio_device_.get(), base::UnguessableToken(),
                                   kUnauthorizedDeviceId));
    audio_device_->RequestDeviceAuthorization();
    audio_device_->GetOutputDeviceInfoAsync(base::BindOnce(
        &AudioOutputDeviceTest::OnDeviceInfoReceived, base::Unretained(this)));
    task_env_.FastForwardBy(base::TimeDelta());
  
    OutputDeviceInfo info;
    constexpr auto kExpectedStatus = OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED;
    EXPECT_CALL(*this, OnDeviceInfoReceived(_))
        .WillOnce(testing::SaveArg<0>(&info));
    ReceiveAuthorization(kExpectedStatus);
  
    task_env_.FastForwardUntilNoTasksRemain();
    EXPECT_EQ(kExpectedStatus, info.device_status());
    EXPECT_EQ(kUnauthorizedDeviceId, info.device_id());
    EXPECT_TRUE(
        AudioParameters::UnavailableDeviceParams().Equals(info.output_params()));
  
    audio_device_->Stop();
    task_env_.FastForwardBy(base::TimeDelta());
  }
  
  TEST_F(AudioOutputDeviceTest, GetOutputDeviceInfoAsync_Okay) {
    CreateDevice(kDefaultDeviceId, base::TimeDelta());
    EXPECT_CALL(
        *audio_output_ipc_,
        RequestDeviceAuthorization(audio_device_.get(), base::UnguessableToken(),
                                   kDefaultDeviceId));
    audio_device_->RequestDeviceAuthorization();
    audio_device_->GetOutputDeviceInfoAsync(base::BindOnce(
        &AudioOutputDeviceTest::OnDeviceInfoReceived, base::Unretained(this)));
    task_env_.FastForwardBy(base::TimeDelta());
  
    OutputDeviceInfo info;
    constexpr auto kExpectedStatus = OUTPUT_DEVICE_STATUS_OK;
    EXPECT_CALL(*this, OnDeviceInfoReceived(_))
        .WillOnce(testing::SaveArg<0>(&info));
    ReceiveAuthorization(kExpectedStatus);
  
    task_env_.FastForwardUntilNoTasksRemain();
    EXPECT_EQ(kExpectedStatus, info.device_status());
    EXPECT_EQ(kDefaultDeviceId, info.device_id());
    EXPECT_TRUE(default_audio_parameters_.Equals(info.output_params()));
  
    audio_device_->Stop();
    task_env_.FastForwardBy(base::TimeDelta());
  }
  
  TEST_F(AudioOutputDeviceTest, StreamIsFlushed) {
    StartAudioDevice();
    FlushAudioDevice();
    StopAudioDevice();
  }
  
  }  // namespace media.
  