910e62b5创建于 1月15日历史提交
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/audio/mac/catap_audio_input_stream.h"

#include <CoreAudioTypes/CoreAudioBaseTypes.h>
#import <Foundation/Foundation.h>

#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "base/containers/span.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/sys_string_conversions.h"
#include "media/audio/application_loopback_device_helper.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/audio_manager.h"
#include "media/audio/mac/audio_loopback_input_mac.h"
#include "media/audio/mac/catap_api.h"
#include "media/base/audio_bus.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace media {

bool operator==(const AudioObjectPropertyAddress& x,
                const AudioObjectPropertyAddress& y) {
  return x.mSelector == y.mSelector && x.mScope == y.mScope &&
         x.mElement == y.mElement;
}

namespace {
void LogToStderr(const std::string& message) {
  LOG(INFO) << message;
}

constexpr int kNumberOfChannelsMono = 1;
constexpr int kNumberOfChannelsStereo = 2;

// A function pointer to this function is used as an identifier for the tap IO
// process ID.
OSStatus AudioDeviceIoProcIdFunction(AudioDeviceID,
                                     const AudioTimeStamp*,
                                     const AudioBufferList*,
                                     const AudioTimeStamp*,
                                     AudioBufferList*,
                                     const AudioTimeStamp*,
                                     void*) {
  return noErr;
}
constexpr AudioDeviceIOProcID kTapIoProcId = AudioDeviceIoProcIdFunction;

// Arbitrary numbers that identifies the aggregate device and tap.
constexpr AudioObjectID kAggregateDeviceId = 17;
constexpr AudioObjectID kTap = 23;

constexpr AudioObjectID kDefaultOutputId = 29;
constexpr std::string kDefaultOutputUID = "default_output_uid";

API_AVAILABLE(macos(14.2))
const CatapAudioInputStream::AudioDeviceIds kDefaultOutputIds(
    kDefaultOutputId,
    kDefaultOutputUID);

API_AVAILABLE(macos(14.2))
const CatapAudioInputStream::AudioDeviceIds kNoDefaultOutputIds;

const AudioObjectPropertyAddress kDeviceIsAliveAddress = {
    kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal,
    kAudioObjectPropertyElementMain};

const AudioObjectPropertyAddress kDefaultOutputDevicePropertyAddress = {
    kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal,
    kAudioObjectPropertyElementMain};

const AudioObjectPropertyAddress kVirtualFormatAddress = {
    kAudioStreamPropertyVirtualFormat, kAudioObjectPropertyScopeGlobal,
    kAudioObjectPropertyElementMain};

const AudioObjectPropertyAddress kSampleRateAddress = {
    kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal,
    kAudioObjectPropertyElementMain};

const AudioObjectPropertyAddress kBufferFrameSizeAddress = {
    kAudioDevicePropertyBufferFrameSize, kAudioObjectPropertyScopeGlobal,
    kAudioObjectPropertyElementMain};

const AudioObjectPropertyAddress kProcessObjectListAddress = {
    kAudioHardwarePropertyProcessObjectList, kAudioObjectPropertyScopeGlobal,
    kAudioObjectPropertyElementMain};

const AudioObjectPropertyAddress kProcessPidAddress = {
    kAudioProcessPropertyPID, kAudioObjectPropertyScopeGlobal,
    kAudioObjectPropertyElementMain};

const AudioObjectPropertyAddress kTapDescriptionAddress = {
    kAudioTapPropertyDescription, kAudioObjectPropertyScopeGlobal,
    kAudioObjectPropertyElementMain};

// Fake for the AudioInputCallback.
class FakeAudioInputCallback : public AudioInputStream::AudioInputCallback {
 public:
  void OnData(const AudioBus* bus,
              base::TimeTicks capture_time,
              double volume,
              const AudioGlitchInfo& glitch_info) override {
    ++on_data_call_count_;
    last_capture_time_ = capture_time;
    last_number_of_channels_ = bus->channels();
    last_number_of_frames_ = bus->frames();
  }

  void OnError() override { on_error_call_count_++; }

  int on_data_call_count() const { return on_data_call_count_; }
  int on_error_call_count() const { return on_error_call_count_; }
  base::TimeTicks last_capture_time() const { return last_capture_time_; }
  int last_number_of_channels() const { return last_number_of_channels_; }
  int last_number_of_frames() const { return last_number_of_frames_; }

 private:
  int on_data_call_count_ = 0;
  int on_error_call_count_ = 0;
  base::TimeTicks last_capture_time_;
  int last_number_of_channels_ = 0;
  int last_number_of_frames_ = 0;
};

// Fake for all CoreAudio API calls.
//
// This class provides a fake implementation of the `CatapApi` interface for use
// in unit tests. It allows tests to simulate the behavior of the CoreAudio API
// and verify that `CatapAudioInputStream` interacts with it correctly.
//
// The typical usage pattern is:
// 1. Create an instance of `FakeCatapApi`.
// 2. (Optional) Configure its behavior by setting the public member variables
//    in the "Public properties that can be modified by the tests" section.
//    For example, set `with_permissions` to `false` to simulate a scenario
//    where the user has not granted screen capture permissions.
// 3. Pass the `FakeCatapApi` instance to the `CatapAudioInputStream` under
//    test.
// 4. Run the code under test.
// 5. Inspect the public member variables in the "Variables that can be
//    inspected by the tests" section to verify that the `CatapAudioInputStream`
//    called the correct `CatapApi` functions with the expected parameters.
//    For example, check that `created_aggregate_device` is `true`.
class FakeCatapApi : public CatapApi {
 public:
  FakeCatapApi() = default;
  ~FakeCatapApi() override = default;

  // CatapApi:
  OSStatus AudioHardwareCreateAggregateDevice(
      CFDictionaryRef in_device_properties,
      AudioDeviceID* out_device) override {
    if (should_fail_create_aggregate_device) {
      return -1;
    }
    created_aggregate_device = true;
    *out_device = kAggregateDeviceId;
    ++create_aggregate_device_count;
    return noErr;
  }
  OSStatus AudioDeviceCreateIOProcID(
      AudioDeviceID in_device,
      AudioDeviceIOProc proc,
      void* in_client_data,
      AudioDeviceIOProcID* out_proc_id) override {
    io_proc_id_created_for_device = in_device;
    audio_proc = proc;
    client_data = in_client_data;
    *out_proc_id = kTapIoProcId;
    return noErr;
  }
  OSStatus AudioObjectGetPropertyDataSize(
      AudioObjectID in_object_id,
      const AudioObjectPropertyAddress* in_address,
      UInt32 in_qualifier_data_size,
      const void* in_qualifier_data,
      UInt32* ioDataSize) override {
    if (*in_address == kProcessObjectListAddress) {
      *ioDataSize = process_audio_devices.size() * sizeof(AudioDeviceID);
      return noErr;
    }
    return noErr;
  }
  OSStatus AudioObjectGetPropertyData(
      AudioObjectID in_object_id,
      const AudioObjectPropertyAddress* in_address,
      UInt32 in_qualifier_data_size,
      const void* in_qualifier_data,
      UInt32* ioDataSize,
      void* outData) override {
    if (*in_address == kProcessObjectListAddress) {
      *ioDataSize = process_audio_devices.size() * sizeof(AudioDeviceID);
      base::span UNSAFE_BUFFERS(
          device_ids(reinterpret_cast<AudioDeviceID*>(outData),
                     process_audio_devices.size()));
      for (size_t i = 0; i < process_audio_devices.size(); ++i) {
        device_ids[i] = process_audio_devices[i];
      }
      return noErr;
    }
    if (*in_address == kProcessPidAddress) {
      auto it = process_pids.find(in_object_id);
      if (it != process_pids.end()) {
        *reinterpret_cast<pid_t*>(outData) = it->second;
        *ioDataSize = sizeof(pid_t);
        return noErr;
      }
      return -1;
    }
    if (*in_address == kDeviceIsAliveAddress) {
      *reinterpret_cast<UInt32*>(outData) = device_is_alive;
      *ioDataSize = sizeof(UInt32);
      return noErr;
    }
    if (*in_address == kSampleRateAddress) {
      if (!last_set_sample_rate.has_value()) {
        return -1;
      }
      *reinterpret_cast<Float64*>(outData) = *last_set_sample_rate;
      *ioDataSize = sizeof(Float64);
      return noErr;
    }
    if (*in_address == kTapDescriptionAddress) {
      // The tap description is returned as an ARC-managed object, so we need to
      // create a retained copy.
      *reinterpret_cast<void**>(outData) =
          (__bridge_retained void*)last_tap_description;
      *ioDataSize = sizeof(CATapDescription*);
      return noErr;
    }
    if (*in_address == kVirtualFormatAddress) {
      AudioStreamBasicDescription* stream_format =
          reinterpret_cast<AudioStreamBasicDescription*>(outData);
      stream_format->mChannelsPerFrame = number_of_device_channels;
      *ioDataSize = sizeof(AudioStreamBasicDescription);
      return noErr;
    }
    return noErr;
  }
  OSStatus AudioObjectSetPropertyData(
      AudioObjectID in_object_id,
      const AudioObjectPropertyAddress* in_address,
      UInt32 in_qualifier_data_size,
      const void* in_qualifier_data,
      UInt32 in_data_size,
      const void* in_data) override {
    if (*in_address == kSampleRateAddress) {
      last_set_sample_rate = *reinterpret_cast<const Float64*>(in_data);
      ++set_sample_rate_count;
    } else if (*in_address == kBufferFrameSizeAddress) {
      last_set_frames_per_buffer = *reinterpret_cast<const UInt32*>(in_data);
      ++set_frames_per_buffer_count;
    } else if (*in_address == kTapDescriptionAddress) {
      ++set_tap_description_count;
      return with_permissions ? noErr : -1;
    } else {
      return -1;
    }
    return noErr;
  }

  API_AVAILABLE(macos(14.2))
  OSStatus AudioHardwareCreateProcessTap(CATapDescription* in_description,
                                         AudioObjectID* out_tap) override {
    last_tap_description = in_description;
    *out_tap = kTap;
    return noErr;
  }
  OSStatus AudioDeviceStart(AudioDeviceID in_device,
                            AudioDeviceIOProcID in_proc_id) override {
    started_device = in_device;
    started_proc_id = in_proc_id;
    return noErr;
  }
  OSStatus AudioDeviceStop(AudioDeviceID in_device,
                           AudioDeviceIOProcID in_proc_id) override {
    stopped_device = in_device;
    stopped_proc_id = in_proc_id;
    return noErr;
  }
  OSStatus AudioDeviceDestroyIOProcID(AudioDeviceID in_device,
                                      AudioDeviceIOProcID in_proc_id) override {
    destroyed_io_proc_id_for_device = in_device;
    destroyed_io_proc_id = in_proc_id;
    client_data = nullptr;
    return noErr;
  }
  OSStatus AudioHardwareDestroyAggregateDevice(
      AudioDeviceID in_device) override {
    destroyed_aggregate_device = in_device;
    return noErr;
  }
  OSStatus AudioHardwareDestroyProcessTap(AudioObjectID in_tap) override {
    destroyed_process_tap = in_tap;
    return noErr;
  }

  OSStatus AudioObjectAddPropertyListenerBlock(
      AudioObjectID in_object_id,
      const AudioObjectPropertyAddress* in_address,
      dispatch_queue_t in_dispatch_queue,
      AudioObjectPropertyListenerBlock in_listener) override {
    property_listener_block = in_listener;
    last_added_property_listener_address = *in_address;
    ++property_listener_block_count;
    return noErr;
  }
  OSStatus AudioObjectRemovePropertyListenerBlock(
      AudioObjectID in_object_id,
      const AudioObjectPropertyAddress* in_address,
      dispatch_queue_t in_dispatch_queue,
      AudioObjectPropertyListenerBlock in_listener) override {
    last_removed_property_listener_address = *in_address;
    --property_listener_block_count;

    if (property_listener_block_count == 0) {
      property_listener_block = nil;
    }
    return noErr;
  }

  // Public properties that can be modified by the tests.

  // If `true`, `AudioObjectSetPropertyData()` will return `noErr` when setting
  // the tap description. Otherwise it will return an error, which simulates
  // that the user has not given screen capture permissions.
  bool with_permissions = true;
  // If `true`, the fake device will be reported as "alive". If `false` it will
  // be reported as not alive. This is used to simulate device disconnections.
  bool device_is_alive = true;
  // Used when the `AudioObjectGetPropertyData()` is called to get the stream
  // format of the default output device. Can be overriden to simulate a mono
  // device.
  int number_of_device_channels = kNumberOfChannelsStereo;
  // Used to simulate the list of process audio device IDs that belong to
  // different processes.
  std::vector<AudioDeviceID> process_audio_devices;
  // The key is the device ID and the value is the process ID. This is used to
  // map device IDs to process IDs.
  std::map<AudioDeviceID, pid_t> process_pids;

  // Variables that can be inspected by the tests.
  // The following variables are set when the corresponding `CatapApi` function
  // is called. Tests can inspect them to verify that the code under test calls
  // the correct functions with the expected parameters.
  bool created_aggregate_device = false;
  AudioDeviceID io_proc_id_created_for_device = 0;
  AudioDeviceID started_device = 0;
  AudioDeviceIOProcID started_proc_id = nullptr;
  AudioDeviceID stopped_device = 0;
  AudioDeviceIOProcID stopped_proc_id = nullptr;
  AudioDeviceID destroyed_io_proc_id_for_device = 0;
  AudioDeviceIOProcID destroyed_io_proc_id = nullptr;
  AudioDeviceID destroyed_aggregate_device = 0;
  AudioObjectID destroyed_process_tap = 0;

  // The last sample rate that was set with `AudioObjectSetPropertyData()`. This
  // is also the value that is returned by `AudioObjectGetPropertyData()` when
  // the sample rate is requested.
  std::optional<Float64> last_set_sample_rate;
  // The last frames per buffer that was set with
  // `AudioObjectSetPropertyData()`.
  std::optional<UInt32> last_set_frames_per_buffer;
  // The last tap description passed to `AudioHardwareCreateProcessTap()`.
  // It's an ARC-managed object that is also returned by
  // `AudioObjectGetPropertyData()` when a tap description is requested.
  CATapDescription* last_tap_description = nullptr;
  // The callback function pointer that was passed to
  // `AudioDeviceCreateIOProcID()`.
  AudioDeviceIOProc audio_proc = nullptr;
  // The client data that was passed to `AudioDeviceCreateIOProcID()`.
  raw_ptr<void> client_data = nullptr;
  // Counters for how many times a specific property was set.
  int set_sample_rate_count = 0;
  int set_frames_per_buffer_count = 0;
  int set_tap_description_count = 0;
  // Counter for how many property listener blocks are currently active.
  // This should be 0 when the stream is closed.
  int property_listener_block_count = 0;
  // Counter for how many times an aggregate device is created.
  int create_aggregate_device_count = 0;
  // The last property listener block that was added with
  // `AudioObjectAddPropertyListenerBlock()`.
  AudioObjectPropertyListenerBlock property_listener_block = nil;
  // The address of the last property listener that was added with
  // `AudioObjectAddPropertyListenerBlock()`.
  AudioObjectPropertyAddress last_added_property_listener_address;
  // The address of the last property listener that was removed with
  // `AudioObjectRemovePropertyListenerBlock()`.
  AudioObjectPropertyAddress last_removed_property_listener_address;
  // If the call to `AudioHardwareCreateAggregateDevice()` will fail.
  bool should_fail_create_aggregate_device = false;
};

}  // namespace

class CatapAudioInputStreamTest : public testing::Test {
 public:
  CatapAudioInputStreamTest() = default;
  ~CatapAudioInputStreamTest() override = default;

  API_AVAILABLE(macos(14.2))
  void CreateStream(bool with_permissions = true,
                    const std::string& device_id =
                        media::AudioDeviceDescription::kLoopbackInputDeviceId) {
    CreateStream(with_permissions, device_id, kDefaultOutputIds);
  }

  API_AVAILABLE(macos(14.2))
  void CreateStream(
      bool with_permissions,
      const std::string& device_id,
      const CatapAudioInputStream::AudioDeviceIds default_device_ids) {
    auto fake_catap_api_object = std::make_unique<FakeCatapApi>();
    fake_catap_api_object->with_permissions = with_permissions;
    fake_catap_api_ = fake_catap_api_object.get();

    auto returning_lambda = [](const CatapAudioInputStream::AudioDeviceIds& ids)
        -> CatapAudioInputStream::AudioDeviceIds { return ids; };
    auto device_id_callback =
        base::BindRepeating(returning_lambda, default_device_ids);

    stream_ = CreateCatapAudioInputStreamForTesting(
        AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
                        ChannelLayoutConfig::Stereo(), kLoopbackSampleRate,
                        kCatapLoopbackDefaultFramesPerBuffer),
        device_id, base::BindRepeating(LogToStderr), base::DoNothing(),
        std::move(fake_catap_api_object), std::move(device_id_callback));
    EXPECT_TRUE(stream_);
  }

  API_AVAILABLE(macos(14.2))
  void CheckSuccessfulOpen() {
      EXPECT_EQ(fake_catap_api()->io_proc_id_created_for_device,
                kAggregateDeviceId);
      EXPECT_EQ(fake_catap_api()->set_sample_rate_count, 1);
      EXPECT_FLOAT_EQ(fake_catap_api()->last_set_sample_rate.value(),
                      kLoopbackSampleRate);
      EXPECT_EQ(fake_catap_api()->set_frames_per_buffer_count, 1);
      EXPECT_FLOAT_EQ(fake_catap_api()->last_set_frames_per_buffer.value(),
                      kCatapLoopbackDefaultFramesPerBuffer);
      EXPECT_EQ(fake_catap_api()->set_tap_description_count, 1);
  }

  API_AVAILABLE(macos(14.2))
  void EnsureStreamIsActive() {
      ASSERT_NE(fake_catap_api()->audio_proc, nullptr);
      const AudioTimeStamp* in_now = nullptr;
      const uint32_t data_byte_size =
          kCatapLoopbackDefaultFramesPerBuffer * sizeof(Float32) *
          fake_catap_api()->number_of_device_channels;
      std::vector<uint8_t> data_buffer(data_byte_size);

      AudioBufferList input_data;
      input_data.mNumberBuffers = 1;
      AudioBuffer& input_buffer = input_data.mBuffers[0];
      input_buffer.mNumberChannels =
          fake_catap_api()->number_of_device_channels;
      input_buffer.mDataByteSize = data_byte_size;
      input_buffer.mData = data_buffer.data();

      AudioTimeStamp input_time;
      input_time.mFlags = kAudioTimeStampHostTimeValid;
      input_time.mHostTime = mach_absolute_time();
      AudioBufferList* output_data = nullptr;
      const AudioTimeStamp* output_time = nullptr;

      fake_catap_api()->audio_proc(0, in_now, &input_data, &input_time,
                                   output_data, output_time,
                                   fake_catap_api()->client_data);
      EXPECT_GE(fake_callback_.on_data_call_count(), 1);
  }

  void TearDown() override {
    if (@available(macOS 14.2, *)) {
      if (!stream_) {
        return;
      }
      stream_->Stop();
      stream_->Close();
      EXPECT_EQ(fake_catap_api_->destroyed_io_proc_id_for_device,
                kAggregateDeviceId);
      EXPECT_EQ(fake_catap_api_->destroyed_io_proc_id, kTapIoProcId);
      EXPECT_EQ(fake_catap_api_->destroyed_aggregate_device,
                kAggregateDeviceId);
      EXPECT_EQ(fake_catap_api_->destroyed_process_tap, kTap);
      fake_catap_api_ = nullptr;
      stream_.ClearAndDelete();
    }
  }

  API_AVAILABLE(macos(14.2)) FakeCatapApi* fake_catap_api() {
    return fake_catap_api_;
  }

 protected:
  raw_ptr<AudioInputStream> stream_;
  raw_ptr<FakeCatapApi> fake_catap_api_;
  FakeAudioInputCallback fake_callback_;
};

TEST_F(CatapAudioInputStreamTest, CreateAndInitializeWithPermissions) {
  if (@available(macOS 14.2, *)) {
    CreateStream();
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);

    CheckSuccessfulOpen();
    EXPECT_EQ(std::string(base::SysNSStringToUTF8(
                  [fake_catap_api()->last_tap_description deviceUID])),
              kDefaultOutputUID);
    EXPECT_EQ([[fake_catap_api()->last_tap_description stream] intValue], 0);
    EXPECT_EQ([fake_catap_api()->last_tap_description isMuted], CATapUnmuted);
  }
}

TEST_F(CatapAudioInputStreamTest, CreateAndFailToInitializeWithoutPermissions) {
  if (@available(macOS 14.2, *)) {
    CreateStream(/*with_permissions=*/false);
    EXPECT_EQ(stream_->Open(),
              AudioInputStream::OpenOutcome::kFailedSystemPermissions);
    EXPECT_EQ(fake_catap_api()->set_tap_description_count, 1);
  }
}

TEST_F(CatapAudioInputStreamTest, DoubleOpenResultsInkAlreadyOpen) {
  if (@available(macOS 14.2, *)) {
    CreateStream();
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kAlreadyOpen);
  }
}

TEST_F(CatapAudioInputStreamTest, CaptureSomeAudioData) {
  if (@available(macOS 14.2, *)) {
    CreateStream();
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);
    stream_->Start(&fake_callback_);
    EXPECT_EQ(fake_catap_api()->started_device, kAggregateDeviceId);
    EXPECT_EQ(fake_catap_api()->started_proc_id, kTapIoProcId);

    ASSERT_NE(fake_catap_api()->audio_proc, nullptr);
    // Simulate a call to `audio_proc` with some data.
    const AudioTimeStamp* in_now = nullptr;
    const uint32_t data_byte_size = kCatapLoopbackDefaultFramesPerBuffer *
                                    sizeof(Float32) * kNumberOfChannelsStereo;
    ;
    std::vector<uint8_t> data_buffer(data_byte_size);

    AudioBufferList input_data;
    input_data.mNumberBuffers = 1;
    AudioBuffer& input_buffer = input_data.mBuffers[0];
    input_buffer.mNumberChannels = kNumberOfChannelsStereo;
    input_buffer.mDataByteSize = data_byte_size;
    input_buffer.mData = data_buffer.data();

    AudioTimeStamp input_time;
    input_time.mFlags = kAudioTimeStampHostTimeValid;
    input_time.mHostTime = mach_absolute_time();
    AudioBufferList* output_data = nullptr;
    const AudioTimeStamp* output_time = nullptr;

    fake_catap_api()->audio_proc(0, in_now, &input_data, &input_time,
                                 output_data, output_time,
                                 fake_catap_api()->client_data);
    EXPECT_GE(fake_callback_.on_data_call_count(), 1);

    stream_->Stop();
    EXPECT_EQ(fake_catap_api()->stopped_device, kAggregateDeviceId);
    EXPECT_EQ(fake_catap_api()->stopped_proc_id, kTapIoProcId);
  }
}

TEST_F(CatapAudioInputStreamTest, CaptureSomeAudioDataMissingHostTime) {
  if (@available(macOS 14.2, *)) {
    CreateStream();
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);
    stream_->Start(&fake_callback_);
    EXPECT_EQ(fake_catap_api()->started_device, kAggregateDeviceId);
    EXPECT_EQ(fake_catap_api()->started_proc_id, kTapIoProcId);

    ASSERT_NE(fake_catap_api()->audio_proc, nullptr);
    // Simulate a call to `audio_proc_` with some data.
    const AudioTimeStamp* in_now = nullptr;
    const uint32_t data_byte_size = kCatapLoopbackDefaultFramesPerBuffer *
                                    sizeof(Float32) * kNumberOfChannelsStereo;
    std::vector<uint8_t> data_buffer(data_byte_size);

    AudioBufferList input_data;
    input_data.mNumberBuffers = 1;
    AudioBuffer& input_buffer = input_data.mBuffers[0];
    input_buffer.mNumberChannels = kNumberOfChannelsStereo;
    input_buffer.mDataByteSize = data_byte_size;
    input_buffer.mData = data_buffer.data();

    AudioTimeStamp input_time;
    input_time.mFlags = kAudioTimeStampHostTimeValid;
    input_time.mHostTime = mach_absolute_time();
    AudioBufferList* output_data = nullptr;
    const AudioTimeStamp* output_time = nullptr;

    fake_catap_api()->audio_proc(0, in_now, &input_data, &input_time,
                                 output_data, output_time,
                                 fake_catap_api()->client_data);

    // Simulate one more captured frame without a host timestamp. Expect the
    // timestamp of the next OnData() call to be incremented by one buffer
    // duration.
    base::TimeTicks previous_capture_timestamp =
        base::TimeTicks::FromMachAbsoluteTime(input_time.mHostTime);
    input_time.mFlags = 0;
    input_time.mHostTime = 0;

    fake_catap_api()->audio_proc(0, in_now, &input_data, &input_time,
                                 output_data, output_time,
                                 fake_catap_api()->client_data);

    base::TimeDelta kExpectedBufferDuration = base::Milliseconds(
        1000 * kCatapLoopbackDefaultFramesPerBuffer / kLoopbackSampleRate);
    base::TimeDelta kCaptureTimeTolerance = base::Milliseconds(1);
    EXPECT_LE((fake_callback_.last_capture_time() - previous_capture_timestamp -
               kExpectedBufferDuration)
                  .magnitude(),
              kCaptureTimeTolerance);

    stream_->Stop();
    EXPECT_EQ(fake_catap_api()->stopped_device, kAggregateDeviceId);
    EXPECT_EQ(fake_catap_api()->stopped_proc_id, kTapIoProcId);
  }
}

TEST_F(CatapAudioInputStreamTest, LoopbackWithoutChromeId) {
  if (@available(macOS 14.2, *)) {
    CreateStream(
        /*with_permissions=*/true,
        /*device_id=*/media::AudioDeviceDescription::kLoopbackWithoutChromeId);

    // Arbitrary number of CoreAudio process audio device IDs to be returned by
    // GetProcessAudioDeviceIds.
    constexpr AudioDeviceID kChromeProcessDeviceId = 1;
    constexpr AudioDeviceID kOtherProcessDeviceId = 2;
    fake_catap_api()->process_audio_devices = {kChromeProcessDeviceId,
                                               kOtherProcessDeviceId};
    fake_catap_api()->process_pids[kChromeProcessDeviceId] = getpid();
    fake_catap_api()->process_pids[kOtherProcessDeviceId] = getpid() + 1;

    // Initialize the stream.
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);

    // The device with the current process ID should be excluded.
    std::set<AudioObjectID> device_ids_to_exclude = {kChromeProcessDeviceId};
    EXPECT_EQ([fake_catap_api()->last_tap_description processes].count,
              device_ids_to_exclude.size());
    // For system audio loopback, the `processes` list contains the specific
    // `AudioObjects` we want to exclude from capture. Therefore, the
    // `exclusive` flag must be true.
    EXPECT_TRUE([fake_catap_api()->last_tap_description isExclusive]);
    for (NSNumber* device_id_number in
         [fake_catap_api()->last_tap_description processes]) {
      EXPECT_TRUE(device_ids_to_exclude.count(
          static_cast<AudioObjectID>([device_id_number intValue])));
    }
  }
}

TEST_F(CatapAudioInputStreamTest, ApplicationLoopback) {
  if (@available(macOS 14.2, *)) {
    base::ProcessId process_id = getpid();
    CreateStream(
        /*with_permissions=*/true,
        /*device_id=*/media::CreateApplicationLoopbackDeviceId(process_id));

    // Arbitrary number of CoreAudio process audio device IDs to be returned by
    // GetProcessAudioDeviceIds.
    constexpr AudioDeviceID kProcessFirstDeviceId = 1;
    constexpr AudioDeviceID kProcessSecondDeviceId = 2;
    constexpr AudioDeviceID kOtherProcessDeviceId = 3;
    fake_catap_api()->process_audio_devices = {
        kProcessFirstDeviceId, kProcessSecondDeviceId, kOtherProcessDeviceId};
    fake_catap_api()->process_pids[kProcessFirstDeviceId] = process_id;
    fake_catap_api()->process_pids[kProcessSecondDeviceId] = process_id;
    fake_catap_api()->process_pids[kOtherProcessDeviceId] = process_id + 1;

    // Initialize the stream.
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);

    // The device with the current process ID should be included.
    std::set<AudioObjectID> device_ids_to_include = {kProcessFirstDeviceId,
                                                     kProcessSecondDeviceId};
    EXPECT_EQ([fake_catap_api()->last_tap_description processes].count,
              device_ids_to_include.size());
    // For application loopback, the `processes` list contains the specific
    // `AudioObjects` we want to capture (include). Therefore, the `exclusive`
    // flag must be false.
    EXPECT_FALSE([fake_catap_api()->last_tap_description isExclusive]);
    for (NSNumber* device_id_number in
         [fake_catap_api()->last_tap_description processes]) {
      EXPECT_TRUE(device_ids_to_include.count(
          static_cast<AudioObjectID>([device_id_number intValue])));
    }

    // In application loopback we capture all output devices. In that case
    // Device UID and stream should not have been set.
    EXPECT_EQ([fake_catap_api()->last_tap_description deviceUID], nullptr);
    EXPECT_EQ([fake_catap_api()->last_tap_description stream], nullptr);
  }
}

TEST_F(CatapAudioInputStreamTest, LoopbackWithMuteDevice) {
  if (@available(macOS 14.2, *)) {
    CreateStream(
        /*with_permissions=*/true,
        /*device_id=*/media::AudioDeviceDescription::kLoopbackWithMuteDeviceId);
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);
    CheckSuccessfulOpen();
    EXPECT_EQ([fake_catap_api()->last_tap_description isMuted], CATapMuted);
  }
}

TEST_F(CatapAudioInputStreamTest, LoopbackWithAllDevices) {
  if (@available(macOS 14.2, *)) {
    CreateStream(
        /*with_permissions=*/true,
        /*device_id=*/media::AudioDeviceDescription::kLoopbackAllDevicesId);
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);
    CheckSuccessfulOpen();
    // Device UID and stream not set indicates that we're capturing all
    // devices.
    EXPECT_EQ([fake_catap_api()->last_tap_description deviceUID], nullptr);
    EXPECT_EQ([fake_catap_api()->last_tap_description stream], nullptr);
  }
}

TEST_F(CatapAudioInputStreamTest, ErrorIfDeviceIsAliveChanges) {
  if (@available(macOS 14.2, *)) {
    CreateStream();
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);
    stream_->Start(&fake_callback_);

    // Simulate that "device is alive" is changed to false.
    fake_catap_api()->device_is_alive = false;

    EXPECT_EQ(fake_callback_.on_error_call_count(), 0);
    fake_catap_api()->property_listener_block(1, &kDeviceIsAliveAddress);
    EXPECT_EQ(fake_callback_.on_error_call_count(), 1);
  }
}

TEST_F(CatapAudioInputStreamTest, NoErrorIfDefaultOutputDeviceChanges) {
  if (@available(macOS 14.2, *)) {
    CreateStream();
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);
    stream_->Start(&fake_callback_);

    // Simulate a change of default output device.
    EXPECT_EQ(fake_callback_.on_error_call_count(), 0);
    fake_catap_api()->property_listener_block(
        1, &kDefaultOutputDevicePropertyAddress);
    EXPECT_EQ(fake_callback_.on_error_call_count(), 0);
  }
}

TEST_F(CatapAudioInputStreamTest, PropertyListenerRemovedOnClose) {
  if (@available(macOS 14.2, *)) {
    CreateStream();
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);

    EXPECT_GT(fake_catap_api()->property_listener_block_count, 0);
    stream_->Close();
    EXPECT_EQ(fake_catap_api()->property_listener_block_count, 0);
    // Prevent TearDown from calling Close() again on a closed stream.
    fake_catap_api_ = nullptr;
    stream_.ClearAndDelete();
  }
}

TEST_F(CatapAudioInputStreamTest, ChannelCountChangeIsIgnored) {
  if (@available(macOS 14.2, *)) {
    CreateStream();
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);
    stream_->Start(&fake_callback_);

    ASSERT_NE(fake_catap_api()->audio_proc, nullptr);

    // Simulate a call to `audio_proc` with correct data.
    const AudioTimeStamp* in_now = nullptr;
    const uint32_t data_byte_size = kCatapLoopbackDefaultFramesPerBuffer *
                                    sizeof(Float32) * kNumberOfChannelsStereo;
    std::vector<uint8_t> data_buffer(data_byte_size);

    AudioBufferList input_data;
    input_data.mNumberBuffers = 1;
    AudioBuffer& input_buffer = input_data.mBuffers[0];
    input_buffer.mNumberChannels = kNumberOfChannelsStereo;
    input_buffer.mDataByteSize = data_byte_size;
    input_buffer.mData = data_buffer.data();

    AudioTimeStamp input_time;
    input_time.mFlags = kAudioTimeStampHostTimeValid;
    input_time.mHostTime = mach_absolute_time();
    AudioBufferList* output_data = nullptr;
    const AudioTimeStamp* output_time = nullptr;

    fake_catap_api()->audio_proc(0, in_now, &input_data, &input_time,
                                 output_data, output_time,
                                 fake_catap_api()->client_data);
    EXPECT_EQ(fake_callback_.on_data_call_count(), 1);
    EXPECT_EQ(fake_callback_.on_error_call_count(), 0);

    // Now simulate a call with a different number of channels.
    input_buffer.mNumberChannels = kNumberOfChannelsMono;
    input_time.mHostTime = mach_absolute_time();

    fake_catap_api()->audio_proc(0, in_now, &input_data, &input_time,
                                 output_data, output_time,
                                 fake_catap_api()->client_data);

    // OnData should not be called again, and no error should be reported.
    EXPECT_EQ(fake_callback_.on_data_call_count(), 1);
    EXPECT_EQ(fake_callback_.on_error_call_count(), 0);

    stream_->Stop();
  }
}

TEST_F(CatapAudioInputStreamTest, FramesPerBufferChangeIsIgnored) {
  if (@available(macOS 14.2, *)) {
    CreateStream();
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);
    stream_->Start(&fake_callback_);

    ASSERT_NE(fake_catap_api()->audio_proc, nullptr);

    // Simulate a call to `audio_proc` with correct data.
    const AudioTimeStamp* in_now = nullptr;
    const uint32_t data_byte_size = kCatapLoopbackDefaultFramesPerBuffer *
                                    sizeof(Float32) * kNumberOfChannelsStereo;
    std::vector<uint8_t> data_buffer(data_byte_size);

    AudioBufferList input_data;
    input_data.mNumberBuffers = 1;
    AudioBuffer& input_buffer = input_data.mBuffers[0];
    input_buffer.mNumberChannels = kNumberOfChannelsStereo;
    input_buffer.mDataByteSize = data_byte_size;
    input_buffer.mData = data_buffer.data();

    AudioTimeStamp input_time;
    input_time.mFlags = kAudioTimeStampHostTimeValid;
    input_time.mHostTime = mach_absolute_time();
    AudioBufferList* output_data = nullptr;
    const AudioTimeStamp* output_time = nullptr;

    fake_catap_api()->audio_proc(0, in_now, &input_data, &input_time,
                                 output_data, output_time,
                                 fake_catap_api()->client_data);
    EXPECT_EQ(fake_callback_.on_data_call_count(), 1);
    EXPECT_EQ(fake_callback_.on_error_call_count(), 0);

    // Now simulate a call with a different number of frames per buffer.
    input_buffer.mDataByteSize = data_byte_size / 2;
    input_time.mHostTime = mach_absolute_time();

    fake_catap_api()->audio_proc(0, in_now, &input_data, &input_time,
                                 output_data, output_time,
                                 fake_catap_api()->client_data);

    // OnData should not be called again, and no error should be reported.
    EXPECT_EQ(fake_callback_.on_data_call_count(), 1);
    EXPECT_EQ(fake_callback_.on_error_call_count(), 0);

    stream_->Stop();
  }
}

TEST_F(CatapAudioInputStreamTest, ReopensOnDeviceChangeForOpenStream) {
  if (@available(macOS 14.2, *)) {
    int expected_create_aggregate_device_count = 0;
    CreateStream();
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);
    ++expected_create_aggregate_device_count;

    // An aggregated device is created once in Open()
    EXPECT_EQ(fake_catap_api()->create_aggregate_device_count,
              expected_create_aggregate_device_count);

    // Check if catap was recreated on default device change.
    fake_catap_api()->property_listener_block(
        1, &kDefaultOutputDevicePropertyAddress);
    ++expected_create_aggregate_device_count;
    EXPECT_EQ(fake_catap_api()->create_aggregate_device_count,
              expected_create_aggregate_device_count);

    stream_->Start(&fake_callback_);

    // Check if the catap stream still calls the OnData() callback.
    EnsureStreamIsActive();
  }
}

TEST_F(CatapAudioInputStreamTest, RestartsOnDeviceChangeForStartedStream) {
  if (@available(macOS 14.2, *)) {
    int expected_create_aggregate_device_count = 0;
    CreateStream();
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);
    ++expected_create_aggregate_device_count;

    // An aggregated device is created once in Open()
    EXPECT_EQ(fake_catap_api()->create_aggregate_device_count,
              expected_create_aggregate_device_count);

    stream_->Start(&fake_callback_);

    // Check if catap was recreated on default device change.
    fake_catap_api()->property_listener_block(
        1, &kDefaultOutputDevicePropertyAddress);
    ++expected_create_aggregate_device_count;
    EXPECT_EQ(fake_catap_api()->create_aggregate_device_count,
              expected_create_aggregate_device_count);

    // Check if the catap stream still calls the OnData() callback.
    EnsureStreamIsActive();
  }
}

TEST_F(CatapAudioInputStreamTest, ReopensOnSamplerateChange) {
  if (@available(macOS 14.2, *)) {
    int expected_create_aggregate_device_count = 0;
    CreateStream();
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);
    ++expected_create_aggregate_device_count;

    // An aggregated device is created once in Open()
    EXPECT_EQ(fake_catap_api()->create_aggregate_device_count,
              expected_create_aggregate_device_count);

    // No change on sample rate notifications if sample rate haven't changed.
    fake_catap_api()->property_listener_block(1, &kSampleRateAddress);
    EXPECT_EQ(fake_catap_api()->create_aggregate_device_count,
              expected_create_aggregate_device_count);

    // Check if catap was recreated on real sampe rate change.
    fake_catap_api()->last_set_sample_rate = 16000;
    fake_catap_api()->property_listener_block(1, &kSampleRateAddress);
    ++expected_create_aggregate_device_count;
    EXPECT_EQ(fake_catap_api()->create_aggregate_device_count,
              expected_create_aggregate_device_count);
  }
}

TEST_F(CatapAudioInputStreamTest, RestartFailingForStartedStream) {
  if (@available(macOS 14.2, *)) {
    CreateStream();
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);
    stream_->Start(&fake_callback_);

    // Trigger a restart that fails
    fake_catap_api()->should_fail_create_aggregate_device = true;
    fake_catap_api()->property_listener_block(
        1, &kDefaultOutputDevicePropertyAddress);

    // Failed restart should trigger an OnError() call.
    EXPECT_EQ(fake_callback_.on_error_call_count(), 1);

    stream_->Stop();
    stream_->Close();
    fake_catap_api_ = nullptr;
    stream_.ClearAndDelete();
  }
}

TEST_F(CatapAudioInputStreamTest, NoDefaultDevice) {
  if (@available(macOS 14.2, *)) {
    // Not providing an UID for default device should cause Open() to fail with
    // unless device id == kLoopbackAllDevicesId.
    CreateStream(
        /*with_permissions=*/true,
        /*device_id=*/media::AudioDeviceDescription::kLoopbackInputDeviceId,
        /*default_device_ids=*/kNoDefaultOutputIds);
    EXPECT_NE(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);
    EXPECT_EQ(fake_catap_api()->create_aggregate_device_count, 0);
    stream_->Close();
    fake_catap_api_ = nullptr;
    stream_.ClearAndDelete();

    // If device id == kLoopbackAllDevicesId it doesn't need an UID for default
    // device, and kSuccess from Open() is expected.
    CreateStream(
        /*with_permissions=*/true,
        /*device_id=*/media::AudioDeviceDescription::kLoopbackAllDevicesId,
        /*default_device_ids=*/kNoDefaultOutputIds);
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);
    EXPECT_EQ(fake_catap_api()->create_aggregate_device_count, 1);
  }
}

TEST_F(CatapAudioInputStreamTest, ReopensStreamSeveralTimes) {
  if (@available(macOS 14.2, *)) {
    int expected_create_aggregate_device_count = 0;
    CreateStream();
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);
    ++expected_create_aggregate_device_count;

    // An aggregated device is created once in Open()
    EXPECT_EQ(fake_catap_api()->create_aggregate_device_count,
              expected_create_aggregate_device_count);

    // Check if catap was recreated on default device change.
    fake_catap_api()->property_listener_block(
        1, &kDefaultOutputDevicePropertyAddress);
    ++expected_create_aggregate_device_count;
    EXPECT_EQ(fake_catap_api()->create_aggregate_device_count,
              expected_create_aggregate_device_count);

    stream_->Start(&fake_callback_);

    // Check if catap was recreated on default device change after Start().
    fake_catap_api()->property_listener_block(
        1, &kDefaultOutputDevicePropertyAddress);
    ++expected_create_aggregate_device_count;
    EXPECT_EQ(fake_catap_api()->create_aggregate_device_count,
              expected_create_aggregate_device_count);

    // No change on sample rate notifications if sample rate haven't changed.
    fake_catap_api()->property_listener_block(1, &kSampleRateAddress);
    EXPECT_EQ(fake_catap_api()->create_aggregate_device_count,
              expected_create_aggregate_device_count);

    // Check if catap was recreated on real sampe rate change.
    fake_catap_api()->last_set_sample_rate = 16000;
    fake_catap_api()->property_listener_block(1, &kSampleRateAddress);
    ++expected_create_aggregate_device_count;
    EXPECT_EQ(fake_catap_api()->create_aggregate_device_count,
              expected_create_aggregate_device_count);

    // Check if the catap stream still calls the OnData() callback.
    EnsureStreamIsActive();
  }
}

TEST_F(CatapAudioInputStreamTest, CaptureStereoDeviceInStereo) {
  if (@available(macOS 14.2, *)) {
    CreateStream();
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);
    CheckSuccessfulOpen();
    EXPECT_FALSE([fake_catap_api()->last_tap_description isMono]);

    stream_->Start(&fake_callback_);
    EnsureStreamIsActive();
    // Verify that the callback receives stereo audio.
    EXPECT_EQ(fake_callback_.last_number_of_channels(),
              kNumberOfChannelsStereo);
    EXPECT_EQ(fake_callback_.last_number_of_frames(),
              kCatapLoopbackDefaultFramesPerBuffer);
  }
}

TEST_F(CatapAudioInputStreamTest, ForceMonoCaptureForMonoDevice) {
  if (@available(macOS 14.2, *)) {
    CreateStream();
    fake_catap_api()->number_of_device_channels = kNumberOfChannelsMono;
    EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kSuccess);
    CheckSuccessfulOpen();
    EXPECT_TRUE([fake_catap_api()->last_tap_description isMono]);

    stream_->Start(&fake_callback_);
    EnsureStreamIsActive();
    // Verify that the callback receives stereo audio.
    EXPECT_EQ(fake_callback_.last_number_of_channels(),
              kNumberOfChannelsStereo);
    EXPECT_EQ(fake_callback_.last_number_of_frames(),
              kCatapLoopbackDefaultFramesPerBuffer);
  }
}

}  // namespace media