blob: 8c7b0280f65f36006630873391b105ebef9c64fb [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 <fuchsia/media/cpp/fidl.h>
#include <lib/zx/clock.h>
#include "src/media/audio/audio_core/test/api/audio_capturer_test_shared.h"
#include "src/media/audio/lib/clock/clone_mono.h"
#include "src/media/audio/lib/clock/testing/clock_test.h"
namespace media::audio::test {
using ASF = fuchsia::media::AudioSampleFormat;
//
// Test cases
//
// AudioCapturer implements the base classes StreamBufferSet and StreamSource, in addition to its
// own FIDL methods.
// TODO(b/318431483): more extensively test StreamBufferSet
// - AddPayloadBuffer(uint32 id, handle<vmo> payload_buffer);
// Also: null or bad handle
// - RemovePayloadBuffer(uint32 id);
// unknown or already-removed id
// - also, apply same tests to AudioRenderer and AudioCapturer
// (although their implementations within AudioCore differ somewhat).
// TODO(b/318432150): more extensively test StreamSource
// - ->OnPacketProduced(StreamPacket packet);
// Always received for every packet - even malformed ones?
// - ->OnEndOfStream();
// Also proper sequence vis-a-vis other completion and disconnect callbacks
// - DiscardAllPacketsNoReply() post-stop
// - all capture StreamPacket flags
// TODO(b/318433705): more extensively test capture data pipeline methods
// - SetPcmStreamType(AudioStreamType stream_type);
// Also when already set, when packets submitted, when started, malformed StreamType
// - CaptureAt(uint32 id, uint32 offset, uint32 num_frames)
// -> (StreamPacket captured_packet);
// Also when in async capture, before format set, before packets submitted
// negative testing: bad id, bad offset, 0/tiny/huge num_frames
// - StartAsyncCapture(uint32 frames_per_packet);
// Also when already started, before format set, before packets submitted
// negative testing: 0/tiny/huge frames_per_packet (bigger than packet)
// - StopAsyncCapture() before format set, before packets submitted
// - StopAsyncCaptureNoReply() same
// - GetStreamType() -> (StreamType stream_type);
// negative testing: before format set
// DiscardAllPackets waits to deliver its completion callback until all packets have returned.
TEST_F(AudioCapturerTestOldAPI, DiscardAllReturnsAfterAllPackets) {
SetFormat();
SetUpPayloadBuffer();
audio_capturer()->CaptureAt(0, 0, 4000, AddCallback("CaptureAt 0"));
audio_capturer()->CaptureAt(0, 4000, 4000, AddCallback("CaptureAt 4000"));
audio_capturer()->CaptureAt(0, 8000, 4000, AddCallback("CaptureAt 8000"));
audio_capturer()->CaptureAt(0, 12000, 4000, AddCallback("CaptureAt 12000"));
// Packets should complete in strict order, with DiscardAllPackets' completion afterward.
audio_capturer()->DiscardAllPackets(AddCallback("DiscardAllPackets"));
ExpectCallbacks();
}
// DiscardAllPackets should succeed, if async capture is completely stopped
TEST_F(AudioCapturerTestOldAPI, DiscardAllAfterAsyncCapture) {
SetFormat();
SetUpPayloadBuffer();
audio_capturer().events().OnPacketProduced = AddCallback("OnPacketProduced");
audio_capturer()->StartAsyncCapture(1600);
ExpectCallbacks();
audio_capturer()->StopAsyncCapture(AddCallback("StopAsyncCapture"));
ExpectCallbacks();
audio_capturer()->DiscardAllPackets(AddCallback("DiscardAllPackets"));
ExpectCallbacks();
}
// DiscardAllPacketsNoReply should succeed, if async capture is completely stopped
TEST_F(AudioCapturerTestOldAPI, DiscardAllNoReplyAfterAsyncCapture) {
SetFormat();
SetUpPayloadBuffer();
audio_capturer().events().OnPacketProduced = AddCallback("OnPacketProduced");
audio_capturer()->StartAsyncCapture(1600);
ExpectCallbacks();
audio_capturer()->StopAsyncCapture(AddCallback("StopAsyncCapture"));
ExpectCallbacks();
audio_capturer()->DiscardAllPacketsNoReply();
RunLoopUntilIdle();
}
// Stopping an async capturer should succeed when all packets are in flight.
TEST_F(AudioCapturerTestOldAPI, StopAsyncWithAllPacketsInFlight) {
const auto kFramesPerPacket = 1600;
const auto kPackets = 10;
const auto kFramesPerSecond = kFramesPerPacket * kPackets; // below we assume 1 packet == 100ms
SetFormat(kFramesPerSecond);
SetUpPayloadBuffer(0, kFramesPerSecond);
// Don't recycle any packets.
int count = 0;
audio_capturer().events().OnPacketProduced =
AddCallback("OnPacketProduced", [&count](auto packet) { count++; });
// Wait until all packets are in flight.
audio_capturer()->StartAsyncCapture(kFramesPerPacket);
RunLoopUntil([&count]() { return count == kPackets; });
// Wait for over one mix period (100ms). This is not necessary for the test, however it
// increases the chance of a mix period running before our StopAsyncCapture call, which
// increases our chance of finding bugs (e.g. https://fxbug.dev/42152274).
usleep(150 * 1000);
audio_capturer()->StopAsyncCapture(AddCallback("StopAsyncCapture"));
ExpectCallbacks();
}
// Test creation and interface independence of GainControl.
// In a number of tests below, we run the message loop to give the AudioCapturer
// or GainControl binding a chance to disconnect, if an error occurred.
TEST_F(AudioCapturerTestOldAPI, BindGainControl) {
// Validate AudioCapturers can create GainControl interfaces.
audio_capturer()->BindGainControl(gain_control().NewRequest());
AddErrorHandler(gain_control(), "AudioCapturer::GainControl");
fuchsia::media::AudioCapturerPtr audio_capturer_2;
audio_core_->CreateAudioCapturer(true, audio_capturer_2.NewRequest());
AddErrorHandler(audio_capturer_2, "AudioCapturer2");
fuchsia::media::audio::GainControlPtr gain_control_2;
audio_capturer_2->BindGainControl(gain_control_2.NewRequest());
AddErrorHandler(gain_control_2, "AudioCapturer::GainControl2");
// What happens to a child gain_control, when a capturer is unbound?
audio_capturer().Unbind();
// What happens to a parent capturer, when a gain_control is unbound?
gain_control_2.Unbind();
// Give audio_capturer() a chance to disconnect the gain_control.
ExpectDisconnect(gain_control());
// Give time for other Disconnects to occur, if they must.
audio_capturer_2->GetStreamType(AddCallback("GetStreamType"));
ExpectCallbacks();
}
//
// Validation of AudioCapturer reference clock methods
// In test cases below of SetReferenceClock calls that should lead to disconnect, we wait for more
// than one mix job, then call another capturer method (SetUsage) to give the capturer time to drop.
// Accept the default clock that is returned if we set no clock.
TEST_F(AudioCapturerClockTestOldAPI, SetRefClockDefault) {
zx::clock ref_clock = GetAndValidateReferenceClock();
clock::testing::VerifyReadOnlyRights(ref_clock);
clock::testing::VerifyIsSystemMonotonic(ref_clock);
clock::testing::VerifyAdvances(ref_clock);
clock::testing::VerifyCannotBeRateAdjusted(ref_clock);
}
// Set a null clock; this represents selecting the AudioCore-generated clock.
TEST_F(AudioCapturerClockTestOldAPI, SetRefClockFlexible) {
audio_capturer()->SetReferenceClock(zx::clock(ZX_HANDLE_INVALID));
zx::clock provided_clock = GetAndValidateReferenceClock();
clock::testing::VerifyReadOnlyRights(provided_clock);
clock::testing::VerifyIsSystemMonotonic(provided_clock);
clock::testing::VerifyAdvances(provided_clock);
clock::testing::VerifyCannotBeRateAdjusted(provided_clock);
}
TEST_F(AudioCapturerClockTestOldAPI, SetRefClockCustom) {
// Set a recognizable custom reference clock -- should be what we receive from GetReferenceClock.
zx::clock dupe_clock, retained_clock, orig_clock = clock::AdjustableCloneOfMonotonic();
zx::clock::update_args args;
args.reset().set_rate_adjust(-100);
ASSERT_EQ(orig_clock.update(args), ZX_OK) << "clock.update with rate_adjust failed";
ASSERT_EQ(orig_clock.duplicate(kClockRights, &dupe_clock), ZX_OK);
ASSERT_EQ(orig_clock.duplicate(kClockRights, &retained_clock), ZX_OK);
audio_capturer()->SetReferenceClock(std::move(dupe_clock));
zx::clock received_clock = GetAndValidateReferenceClock();
clock::testing::VerifyReadOnlyRights(received_clock);
clock::testing::VerifyIsNotSystemMonotonic(received_clock);
clock::testing::VerifyAdvances(received_clock);
clock::testing::VerifyCannotBeRateAdjusted(received_clock);
// We can still rate-adjust our custom clock.
clock::testing::VerifyCanBeRateAdjusted(orig_clock);
clock::testing::VerifyAdvances(orig_clock);
}
// If client-submitted clock has ZX_RIGHT_WRITE, this should be removed upon GetReferenceClock.
TEST_F(AudioCapturerClockTestOldAPI, GetRefClockRemovesWriteRight) {
audio_capturer()->SetReferenceClock(clock::AdjustableCloneOfMonotonic());
zx::clock received_clock = GetAndValidateReferenceClock();
clock::testing::VerifyReadOnlyRights(received_clock);
}
// You can set the reference clock at any time before the payload buffer is added.
TEST_F(AudioCapturerClockTestOldAPI, SetRefClockBeforeBuffer) {
SetFormat();
audio_capturer()->SetReferenceClock(zx::clock(ZX_HANDLE_INVALID));
GetAndValidateReferenceClock();
}
TEST_F(AudioCapturerTest, NoCrashOnChannelCloseAfterStopAsync) {
auto format = Format::Create<ASF::SIGNED_16>(1, 48000).take_value();
CreateInput({{0xff, 0x00}}, format, 48000);
auto capturer = CreateAudioCapturer(format, 48000,
fuchsia::media::AudioCapturerConfiguration::WithInput(
fuchsia::media::InputAudioCapturerConfiguration()));
capturer->fidl()->StartAsyncCapture(480);
RunLoopUntilIdle();
capturer->fidl()->StopAsyncCaptureNoReply();
Unbind(capturer);
RunLoopUntilIdle();
}
// Test capturing when there's no input device. We expect this to work with all the audio captured
// being completely silent.
TEST_F(AudioCapturerTest, CaptureAsyncNoDevice) {
auto format = Format::Create<ASF::SIGNED_16>(1, 16000).take_value();
auto capturer = CreateAudioCapturer(format, 16000,
fuchsia::media::AudioCapturerConfiguration::WithInput(
fuchsia::media::InputAudioCapturerConfiguration()));
// Initialize capture buffers to non-silent values.
capturer->payload().Memset<ASF::SIGNED_16>(0xff);
// Capture a packet and retain it.
std::optional<fuchsia::media::StreamPacket> capture_packet;
capturer->fidl().events().OnPacketProduced = AddCallback(
"OnPacketProduced", [&capture_packet](auto packet) { capture_packet = std::move(packet); });
capturer->fidl()->StartAsyncCapture(1600);
ExpectCallbacks();
capturer->fidl()->StopAsyncCapture(AddCallback("StopAsyncCapture"));
ExpectCallbacks();
// Expect the packet to be silent. Since we initialized the buffer to non-silence we know that
// this silence was populated by audio_core.
EXPECT_TRUE(capture_packet);
EXPECT_EQ(capture_packet->payload_buffer_id, 0u);
EXPECT_NE(capture_packet->payload_size, 0u);
auto buffer = capturer->payload().SnapshotSlice<ASF::SIGNED_16>(capture_packet->payload_offset,
capture_packet->payload_size);
Unbind(capturer);
ASSERT_EQ(1, buffer.format().channels());
for (int64_t frame = 0; frame < buffer.NumFrames(); ++frame) {
ASSERT_EQ(buffer.SampleAt(frame, 0), 0) << "at frame " << frame;
}
}
} // namespace media::audio::test