| // 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 "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; |
| } |
| char data[GetPacketSize()]; |
| memset(data, 0, GetPacketSize()); |
| SendData(start_timestamp_ + base::Seconds(1) * packet_index_ * |
| frames_per_packet_ / |
| stream_type_->frames_per_second, |
| 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 |