chromium/media/fuchsia/audio/fake_audio_capturer.cc

// Copyright 2021 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/fuchsia/audio/fake_audio_capturer.h"

#include <string.h>

#include "base/fuchsia/fuchsia_logging.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/ranges/algorithm.h"
#include "base/types/fixed_array.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace media {

FakeAudioCapturer::FakeAudioCapturer(
    fidl::InterfaceRequest<fuchsia::media::AudioCapturer> request)
    : binding_(this) {
  if (request)
    Bind(std::move(request));
}

FakeAudioCapturer::~FakeAudioCapturer() = default;

void FakeAudioCapturer::Bind(
    fidl::InterfaceRequest<fuchsia::media::AudioCapturer> request) {
  binding_.Bind(std::move(request));
}

size_t FakeAudioCapturer::GetPacketSize() const {
  return frames_per_packet_ * stream_type_->channels * sizeof(float);
}

void FakeAudioCapturer::SetDataGeneration(DataGeneration data_generation) {
  EXPECT_TRUE(!is_active());
  data_generation_ = data_generation;
}

void FakeAudioCapturer::SendData(base::TimeTicks timestamp, void* data) {
  EXPECT_TRUE(buffer_vmo_);
  EXPECT_TRUE(is_active_);

  // Find unused packet.
  auto it = base::ranges::find(packets_usage_, false);

  // Currently tests don't try to send more than 2 packets and the buffer
  // always will have space for at least 2 packets.
  EXPECT_TRUE(it != packets_usage_.end());

  size_t buffer_index = it - packets_usage_.begin();
  size_t buffer_pos = buffer_index * GetPacketSize();

  packets_usage_[buffer_index] = true;

  // Write data to the shared VMO.
  zx_status_t status = buffer_vmo_.write(data, buffer_pos, GetPacketSize());
  ZX_CHECK(status == ZX_OK, status);

  // Send the new packet.
  fuchsia::media::StreamPacket packet;
  packet.payload_buffer_id = kBufferId;
  packet.pts = timestamp.ToZxTime();
  packet.payload_offset = buffer_pos;
  packet.payload_size = GetPacketSize();
  binding_.events().OnPacketProduced(std::move(packet));
}

// fuchsia::media::AudioCapturer implementation.
void FakeAudioCapturer::SetPcmStreamType(
    fuchsia::media::AudioStreamType stream_type) {
  EXPECT_TRUE(!stream_type_.has_value());
  EXPECT_EQ(stream_type.sample_format,
            fuchsia::media::AudioSampleFormat::FLOAT);

  stream_type_ = std::move(stream_type);
}

void FakeAudioCapturer::AddPayloadBuffer(uint32_t id, zx::vmo payload_buffer) {
  EXPECT_EQ(id, kBufferId);
  EXPECT_TRUE(!buffer_vmo_);
  EXPECT_TRUE(stream_type_.has_value());

  buffer_vmo_ = std::move(payload_buffer);
  zx_status_t status = buffer_vmo_.get_size(&buffer_size_);
  ZX_CHECK(status == ZX_OK, status);
}

void FakeAudioCapturer::StartAsyncCapture(uint32_t frames_per_packet) {
  EXPECT_TRUE(buffer_vmo_);
  EXPECT_TRUE(!is_active_);

  is_active_ = true;
  frames_per_packet_ = frames_per_packet;
  size_t num_packets = buffer_size_ / GetPacketSize();

  // AudioCapturer protocol requires that we can fit at least 2 packets in the
  // buffer in async data_generation.
  EXPECT_GE(num_packets, 2U);

  packets_usage_.clear();
  packets_usage_.resize(num_packets, false);

  if (data_generation_ == DataGeneration::AUTOMATIC) {
    start_timestamp_ = base::TimeTicks::Now();
    ProducePackets();
  }
}

void FakeAudioCapturer::StopAsyncCaptureNoReply() {
  is_active_ = false;
  timer_.Stop();
}

void FakeAudioCapturer::ReleasePacket(fuchsia::media::StreamPacket packet) {
  EXPECT_EQ(packet.payload_buffer_id, kBufferId);
  EXPECT_EQ(packet.payload_offset % GetPacketSize(), 0U);
  size_t buffer_index = packet.payload_offset / GetPacketSize();
  EXPECT_LT(buffer_index, packets_usage_.size());
  EXPECT_TRUE(packets_usage_[buffer_index]);
  packets_usage_[buffer_index] = false;
}

void FakeAudioCapturer::NotImplemented_(const std::string& name) {
  ADD_FAILURE() << "Unexpected FakeAudioCapturer call: " << name;
}

void FakeAudioCapturer::ProducePackets() {
  if (!binding_.is_bound()) {
    return;
  }
  base::FixedArray<char> data(GetPacketSize());
  memset(data.data(), 0, data.memsize());
  SendData(start_timestamp_ + base::Seconds(1) * packet_index_ *
                                  frames_per_packet_ /
                                  stream_type_->frames_per_second,
           data.data());
  packet_index_++;
  timer_.Start(FROM_HERE,
               start_timestamp_ +
                   base::Seconds(1) * packet_index_ * frames_per_packet_ /
                       stream_type_->frames_per_second -
                   base::TimeTicks::Now(),
               this, &FakeAudioCapturer::ProducePackets);
}

FakeAudioCapturerFactory::FakeAudioCapturerFactory(
    sys::OutgoingDirectory* outgoing_directory)
    : binding_(outgoing_directory, this) {}

FakeAudioCapturerFactory::~FakeAudioCapturerFactory() = default;

std::unique_ptr<FakeAudioCapturer> FakeAudioCapturerFactory::TakeCapturer() {
  if (capturers_.empty())
    return nullptr;
  auto result = std::move(capturers_.front());
  capturers_.pop_front();
  return result;
}

void FakeAudioCapturerFactory::CreateAudioCapturer(
    fidl::InterfaceRequest<fuchsia::media::AudioCapturer> request,
    bool loopback) {
  capturers_.push_back(std::make_unique<FakeAudioCapturer>());
  capturers_.back()->Bind(std::move(request));
}

void FakeAudioCapturerFactory::NotImplemented_(const std::string& name) {
  ADD_FAILURE() << "Unexpected FakeAudioCapturerFactory call: " << name;
}

}  // namespace media