chromium/media/audio/fuchsia/audio_input_stream_fuchsia_test.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "media/audio/fuchsia/audio_input_stream_fuchsia.h"

#include <fuchsia/media/cpp/fidl_test_base.h>
#include <lib/fidl/cpp/binding.h>

#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/test_component_context_for_process.h"
#include "base/test/task_environment.h"
#include "media/audio/audio_device_description.h"
#include "media/base/audio_parameters.h"
#include "media/base/channel_layout.h"
#include "media/fuchsia/audio/fake_audio_capturer.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace media {

namespace {

constexpr size_t kFramesPerPacket = 480;

class TestCaptureCallback final : public AudioInputStream::AudioInputCallback {
 public:
  TestCaptureCallback() = default;
  ~TestCaptureCallback() override = default;

  TestCaptureCallback(const TestCaptureCallback&) = delete;
  TestCaptureCallback& operator=(const TestCaptureCallback&) = delete;

  bool have_error() const { return have_error_; }

  const std::vector<std::unique_ptr<AudioBus>>& packets() const {
    return packets_;
  }

  // AudioCapturerSource::CaptureCallback implementation.
  void OnData(const AudioBus* source,
              base::TimeTicks capture_time,
              double volume,
              const AudioGlitchInfo& glitch_info) override {
    auto bus = AudioBus::Create(source->channels(), source->frames());
    source->CopyTo(bus.get());
    packets_.push_back(std::move(bus));
  }

  void OnError() override {
    EXPECT_FALSE(have_error_);
    have_error_ = true;
  }

 private:
  std::vector<std::unique_ptr<AudioBus>> packets_;
  bool have_error_ = false;
};

}  // namespace

class AudioInputStreamFuchsiaTest : public testing::Test {
 public:
  AudioInputStreamFuchsiaTest() {}

  ~AudioInputStreamFuchsiaTest() override {
    if (input_stream_) {
      input_stream_->Stop();
      input_stream_.reset();
    }

    base::RunLoop().RunUntilIdle();
  }

  void InitializeCapturer(ChannelLayoutConfig channel_layout_config) {
    base::TestComponentContextForProcess test_context;
    FakeAudioCapturerFactory audio_capturer_factory(
        test_context.additional_services());

    input_stream_ = std::make_unique<AudioInputStreamFuchsia>(
        /*manager=*/nullptr,
        AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
                        channel_layout_config,
                        /*sample_rate=*/48000, kFramesPerPacket),
        AudioDeviceDescription::kDefaultDeviceId);

    AudioInputStream::OpenOutcome result = input_stream_->Open();
    EXPECT_EQ(result, AudioInputStream::OpenOutcome::kSuccess);

    base::RunLoop().RunUntilIdle();

    test_capturer_ = audio_capturer_factory.TakeCapturer();
    ASSERT_TRUE(test_capturer_);
    test_capturer_->SetDataGeneration(
        FakeAudioCapturer::DataGeneration::MANUAL);

    // Verify no other capturers were created.
    ASSERT_FALSE(audio_capturer_factory.TakeCapturer());
  }

  void TestCapture(ChannelLayoutConfig channel_layout_config) {
    InitializeCapturer(channel_layout_config);
    input_stream_->Start(&callback_);
    base::RunLoop().RunUntilIdle();

    size_t num_channels = channel_layout_config.channels();

    // Produce a packet.
    std::vector<float> samples(kFramesPerPacket * num_channels);
    for (size_t i = 0; i < samples.size(); ++i) {
      samples[i] = i;
    }

    base::TimeTicks ts = base::TimeTicks::FromZxTime(100);
    test_capturer_->SendData(ts, samples.data());
    base::RunLoop().RunUntilIdle();

    // Verify that the packet was received.
    ASSERT_EQ(callback_.packets().size(), 1U);
    ASSERT_EQ(callback_.packets()[0]->frames(),
              static_cast<int>(kFramesPerPacket));
    for (size_t i = 0; i < samples.size(); ++i) {
      EXPECT_EQ(samples[i], callback_.packets()[0]->channel(
                                i % num_channels)[i / num_channels]);
    }
  }

 protected:
  base::test::SingleThreadTaskEnvironment task_environment_{
      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};

  std::unique_ptr<FakeAudioCapturer> test_capturer_;
  TestCaptureCallback callback_;
  std::unique_ptr<AudioInputStreamFuchsia> input_stream_;
};

TEST_F(AudioInputStreamFuchsiaTest, CreateAndDestroy) {}

TEST_F(AudioInputStreamFuchsiaTest, InitializeAndDestroy) {
  InitializeCapturer(ChannelLayoutConfig::Mono());
}

TEST_F(AudioInputStreamFuchsiaTest, InitializeAndStart) {
  const auto kChannelLayoutConfig = ChannelLayoutConfig::Mono();
  const auto kNumChannels = kChannelLayoutConfig.channels();

  InitializeCapturer(kChannelLayoutConfig);
  input_stream_->Start(&callback_);
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(test_capturer_->is_active());
  EXPECT_EQ(test_capturer_->GetPacketSize(),
            sizeof(float) * kFramesPerPacket * kNumChannels);

  EXPECT_TRUE(callback_.packets().empty());
}

TEST_F(AudioInputStreamFuchsiaTest, InitializeStereo) {
  const auto kChannelLayoutConfig = ChannelLayoutConfig::Stereo();
  const auto kNumChannels = kChannelLayoutConfig.channels();

  InitializeCapturer(kChannelLayoutConfig);
  input_stream_->Start(&callback_);
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(test_capturer_->is_active());
  EXPECT_EQ(test_capturer_->GetPacketSize(),
            sizeof(float) * kNumChannels * kFramesPerPacket);
}

TEST_F(AudioInputStreamFuchsiaTest, StartAndStop) {
  InitializeCapturer(ChannelLayoutConfig::Stereo());
  input_stream_->Start(&callback_);
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(test_capturer_->is_active());

  input_stream_->Stop();
  base::RunLoop().RunUntilIdle();
}

TEST_F(AudioInputStreamFuchsiaTest, CaptureMono) {
  TestCapture(ChannelLayoutConfig::Mono());
}

TEST_F(AudioInputStreamFuchsiaTest, CaptureStereo) {
  TestCapture(ChannelLayoutConfig::Stereo());
}

TEST_F(AudioInputStreamFuchsiaTest, CaptureTwoPackets) {
  InitializeCapturer(ChannelLayoutConfig::Mono());
  input_stream_->Start(&callback_);
  base::RunLoop().RunUntilIdle();

  // Produce two packets.
  std::vector<float> samples1(kFramesPerPacket);
  std::vector<float> samples2(kFramesPerPacket);
  for (size_t i = 0; i < kFramesPerPacket; ++i) {
    samples1[i] = i;
    samples2[i] = i + 0.2;
  }

  base::TimeTicks ts = base::TimeTicks::FromZxTime(100);
  test_capturer_->SendData(ts, samples1.data());
  test_capturer_->SendData(ts + base::Milliseconds(10), samples2.data());
  base::RunLoop().RunUntilIdle();

  // Verify that both packets were received.
  ASSERT_EQ(callback_.packets().size(), 2U);
  ASSERT_EQ(callback_.packets()[0]->frames(),
            static_cast<int>(kFramesPerPacket));
  ASSERT_EQ(callback_.packets()[1]->frames(),
            static_cast<int>(kFramesPerPacket));
  for (size_t i = 0; i < kFramesPerPacket; ++i) {
    EXPECT_EQ(samples1[i], callback_.packets()[0]->channel(0)[i]);
    EXPECT_EQ(samples2[i], callback_.packets()[1]->channel(0)[i]);
  }
}

TEST_F(AudioInputStreamFuchsiaTest, CaptureAfterStop) {
  InitializeCapturer(ChannelLayoutConfig::Mono());
  input_stream_->Start(&callback_);
  base::RunLoop().RunUntilIdle();
  input_stream_->Stop();

  std::vector<float> samples(kFramesPerPacket);
  base::TimeTicks ts = base::TimeTicks::FromZxTime(100);
  test_capturer_->SendData(ts, samples.data());
  base::RunLoop().RunUntilIdle();

  // Packets produced after Stop() should not be passed to the callback.
  ASSERT_EQ(callback_.packets().size(), 0U);
}

}  // namespace media