blob: 461f25619a336ff2fb325d8d98d00cb552c8a423 [file] [log] [blame]
// Copyright 2020 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/fuchsia/audio/fuchsia_audio_capturer_source.h"
#include <fuchsia/media/cpp/fidl_test_base.h>
#include <lib/fidl/cpp/binding.h>
#include "base/fuchsia/fuchsia_logging.h"
#include "base/test/task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "media/base/channel_layout.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
namespace {
constexpr size_t kFramesPerPacket = 480;
constexpr uint32_t kBufferId = 0;
class TestAudioCapturer final
: public fuchsia::media::testing::AudioCapturer_TestBase {
public:
TestAudioCapturer(
fidl::InterfaceRequest<fuchsia::media::AudioCapturer> request)
: binding_(this, std::move(request)) {}
~TestAudioCapturer() override = default;
TestAudioCapturer(const TestAudioCapturer&) = delete;
TestAudioCapturer& operator=(const TestAudioCapturer&) = delete;
bool is_active() const { return is_active_; }
// Size of a single packet in bytes.
size_t packet_size() const {
return frames_per_packet_ * stream_type_->channels * sizeof(float);
}
void SendData(base::TimeTicks timestamp, void* data) {
CHECK(buffer_vmo_);
CHECK(is_active_);
// Find unused packet.
auto it = std::find(packets_usage_.begin(), packets_usage_.end(), false);
// Currently tests don't try to send more than 2 packets and the buffer
// always will have space for at least 2 packets.
CHECK(it != packets_usage_.end());
size_t buffer_index = it - packets_usage_.begin();
size_t buffer_pos = buffer_index * packet_size();
packets_usage_[buffer_index] = true;
// Write data to the shared VMO.
zx_status_t status = buffer_vmo_.write(data, buffer_pos, packet_size());
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 = packet_size();
binding_.events().OnPacketProduced(std::move(packet));
}
// fuchsia::media::AudioCapturer implementation.
void SetPcmStreamType(fuchsia::media::AudioStreamType stream_type) override {
ASSERT_FALSE(stream_type_.has_value());
ASSERT_EQ(stream_type.sample_format,
fuchsia::media::AudioSampleFormat::FLOAT);
stream_type_ = std::move(stream_type);
}
void AddPayloadBuffer(uint32_t id, zx::vmo payload_buffer) override {
ASSERT_EQ(id, kBufferId);
ASSERT_FALSE(buffer_vmo_);
ASSERT_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 StartAsyncCapture(uint32_t frames_per_packet) override {
ASSERT_TRUE(buffer_vmo_);
ASSERT_FALSE(is_active_);
is_active_ = true;
frames_per_packet_ = frames_per_packet;
size_t num_packets = buffer_size_ / packet_size();
// AudioCapturer protocol requires that we can fit at least 2 packets in the
// buffer in async mode.
ASSERT_GE(num_packets, 2U);
packets_usage_.clear();
packets_usage_.resize(num_packets, false);
}
void ReleasePacket(fuchsia::media::StreamPacket packet) override {
ASSERT_EQ(packet.payload_buffer_id, kBufferId);
ASSERT_EQ(packet.payload_offset % packet_size(), 0U);
size_t buffer_index = packet.payload_offset / packet_size();
ASSERT_LT(buffer_index, packets_usage_.size());
ASSERT_TRUE(packets_usage_[buffer_index]);
packets_usage_[buffer_index] = false;
}
// No other methods are expected to be called.
void NotImplemented_(const std::string& name) override {
FAIL() << ": " << name;
}
private:
fidl::Binding<fuchsia::media::AudioCapturer> binding_;
zx::vmo buffer_vmo_;
uint64_t buffer_size_ = 0;
absl::optional<fuchsia::media::AudioStreamType> stream_type_;
bool is_active_ = false;
size_t frames_per_packet_ = 0;
std::vector<bool> packets_usage_;
};
class TestCaptureCallback final : public AudioCapturerSource::CaptureCallback {
public:
TestCaptureCallback() = default;
~TestCaptureCallback() override = default;
TestCaptureCallback(const TestCaptureCallback&) = delete;
TestCaptureCallback& operator=(const TestCaptureCallback&) = delete;
bool is_started() const { return is_started_; }
bool have_error() const { return have_error_; }
const std::vector<std::unique_ptr<AudioBus>>& packets() const {
return packets_;
}
// AudioCapturerSource::CaptureCallback implementation.
void OnCaptureStarted() override { is_started_ = true; }
void Capture(const AudioBus* audio_source,
base::TimeTicks audio_capture_time,
double volume,
bool key_pressed) override {
EXPECT_TRUE(is_started_);
auto bus =
AudioBus::Create(audio_source->channels(), audio_source->frames());
audio_source->CopyTo(bus.get());
packets_.push_back(std::move(bus));
}
void OnCaptureError(AudioCapturerSource::ErrorCode code,
const std::string& message) override {
EXPECT_FALSE(have_error_);
have_error_ = true;
}
void OnCaptureMuted(bool is_muted) override { FAIL(); }
void OnCaptureProcessorCreated(AudioProcessorControls* controls) override {
FAIL();
}
private:
std::vector<std::unique_ptr<AudioBus>> packets_;
bool is_started_ = false;
bool have_error_ = false;
};
} // namespace
class FuchsiaAudioCapturerSourceTest : public testing::Test {
public:
FuchsiaAudioCapturerSourceTest() {
fidl::InterfaceHandle<fuchsia::media::AudioCapturer> capturer_handle;
test_capturer_ =
std::make_unique<TestAudioCapturer>(capturer_handle.NewRequest());
capturer_source_ = base::MakeRefCounted<FuchsiaAudioCapturerSource>(
std::move(capturer_handle), base::ThreadTaskRunnerHandle::Get());
}
~FuchsiaAudioCapturerSourceTest() override {
capturer_source_->Stop();
capturer_source_ = nullptr;
base::RunLoop().RunUntilIdle();
}
void InitializeCapturer(ChannelLayout layout) {
capturer_source_->Initialize(
AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, layout,
/*sample_rate=*/48000, kFramesPerPacket),
&callback_);
}
void TestCapture(ChannelLayout layout) {
InitializeCapturer(layout);
capturer_source_->Start();
base::RunLoop().RunUntilIdle();
size_t num_channels = ChannelLayoutToChannelCount(layout);
// 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<TestAudioCapturer> test_capturer_;
TestCaptureCallback callback_;
scoped_refptr<FuchsiaAudioCapturerSource> capturer_source_;
};
TEST_F(FuchsiaAudioCapturerSourceTest, CreateAndDestroy) {}
TEST_F(FuchsiaAudioCapturerSourceTest, InitializeAndDestroy) {
InitializeCapturer(CHANNEL_LAYOUT_MONO);
}
TEST_F(FuchsiaAudioCapturerSourceTest, InitializeAndStart) {
const auto kLayout = CHANNEL_LAYOUT_MONO;
const auto kNumChannels = ChannelLayoutToChannelCount(kLayout);
InitializeCapturer(kLayout);
capturer_source_->Start();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(test_capturer_->is_active());
EXPECT_EQ(test_capturer_->packet_size(),
sizeof(float) * kFramesPerPacket * kNumChannels);
EXPECT_TRUE(callback_.is_started());
EXPECT_TRUE(callback_.packets().empty());
}
TEST_F(FuchsiaAudioCapturerSourceTest, InitializeStereo) {
const auto kLayout = CHANNEL_LAYOUT_STEREO;
const auto kNumChannels = ChannelLayoutToChannelCount(kLayout);
InitializeCapturer(kLayout);
capturer_source_->Start();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(test_capturer_->is_active());
EXPECT_EQ(test_capturer_->packet_size(),
sizeof(float) * kNumChannels * kFramesPerPacket);
}
TEST_F(FuchsiaAudioCapturerSourceTest, StartAndStop) {
InitializeCapturer(CHANNEL_LAYOUT_MONO);
capturer_source_->Start();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(test_capturer_->is_active());
capturer_source_->Stop();
base::RunLoop().RunUntilIdle();
}
TEST_F(FuchsiaAudioCapturerSourceTest, CaptureMono) {
TestCapture(CHANNEL_LAYOUT_MONO);
}
TEST_F(FuchsiaAudioCapturerSourceTest, CaptureStereo) {
TestCapture(CHANNEL_LAYOUT_STEREO);
}
TEST_F(FuchsiaAudioCapturerSourceTest, CaptureTwoPackets) {
InitializeCapturer(CHANNEL_LAYOUT_MONO);
capturer_source_->Start();
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(FuchsiaAudioCapturerSourceTest, CaptureAfterStop) {
InitializeCapturer(CHANNEL_LAYOUT_MONO);
capturer_source_->Start();
base::RunLoop().RunUntilIdle();
capturer_source_->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