| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos/ash/services/recording/audio_stream.h" |
| |
| #include "base/memory/aligned_memory.h" |
| #include "base/time/time.h" |
| #include "chromeos/ash/services/recording/audio_capture_test_base.h" |
| #include "chromeos/ash/services/recording/audio_capture_util.h" |
| #include "chromeos/ash/services/recording/recording_service_constants.h" |
| #include "media/audio/simple_sources.h" |
| #include "media/base/audio_bus.h" |
| #include "media/base/audio_parameters.h" |
| #include "media/base/vector_math.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace recording { |
| |
| class AudioStreamTest : public AudioCaptureTestBase { |
| public: |
| AudioStreamTest() = default; |
| AudioStreamTest(const AudioStreamTest&) = delete; |
| AudioStreamTest& operator=(const AudioStreamTest&) = delete; |
| ~AudioStreamTest() override = default; |
| |
| std::unique_ptr<media::AudioBus> ProduceAndAppendAudio( |
| AudioStream& stream, |
| base::TimeTicks timestamp) { |
| auto stream_bus = ProduceAudio(timestamp); |
| auto copy_bus = media::AudioBus::Create(audio_parameters_); |
| stream_bus->CopyTo(copy_bus.get()); |
| stream.AppendAudioBus(std::move(stream_bus), timestamp); |
| return copy_bus; |
| } |
| }; |
| |
| TEST_F(AudioStreamTest, Basic) { |
| AudioStream stream(""); |
| EXPECT_EQ(base::TimeTicks(), stream.begin_timestamp()); |
| EXPECT_EQ(base::TimeTicks(), stream.end_timestamp()); |
| EXPECT_EQ(0, stream.total_frames()); |
| |
| ProduceAndAppendAudio(stream, GetTimestamp(base::Milliseconds(10))); |
| EXPECT_EQ(GetTimestamp(base::Milliseconds(10)), stream.begin_timestamp()); |
| EXPECT_EQ(GetTimestamp(base::Milliseconds(20)), stream.end_timestamp()); |
| EXPECT_EQ(audio_parameters_.frames_per_buffer(), stream.total_frames()); |
| |
| // Using a wrong timestamp (e.g. earlier than the current `end_timestamp()` of |
| // the stream), it will be ignored, and the bus will be appended at the |
| // current end to guarantee monotonically increasing timestamps. |
| ProduceAndAppendAudio(stream, GetTimestamp(base::Milliseconds(10))); |
| EXPECT_EQ(GetTimestamp(base::Milliseconds(10)), stream.begin_timestamp()); |
| EXPECT_EQ(GetTimestamp(base::Milliseconds(30)), stream.end_timestamp()); |
| EXPECT_EQ(2 * audio_parameters_.frames_per_buffer(), stream.total_frames()); |
| } |
| |
| TEST_F(AudioStreamTest, WrongTimestampWhenBusFullyConsumed) { |
| AudioStream stream(""); |
| ProduceAndAppendAudio(stream, GetTimestamp(base::Milliseconds(10))); |
| auto bus_for_consume = media::AudioBus::Create(audio_parameters_); |
| stream.ConsumeAndAccumulateTo( |
| /*destination=*/bus_for_consume.get(), /*destination_start_frame=*/0, |
| /*frames_to_consume=*/bus_for_consume->frames()); |
| EXPECT_TRUE(stream.empty()); |
| EXPECT_EQ(stream.begin_timestamp(), stream.end_timestamp()); |
| EXPECT_EQ(stream.end_timestamp(), GetTimestamp(base::Milliseconds(20))); |
| |
| // Even though the stream is now empty, appending a bus with a wrong timestamp |
| // should still work, and the timestamps of the stream should remain |
| // monotonically increasing. |
| ProduceAndAppendAudio(stream, GetTimestamp(base::Milliseconds(10))); |
| EXPECT_EQ(GetTimestamp(base::Milliseconds(20)), stream.begin_timestamp()); |
| EXPECT_EQ(GetTimestamp(base::Milliseconds(30)), stream.end_timestamp()); |
| EXPECT_EQ(audio_parameters_.frames_per_buffer(), stream.total_frames()); |
| } |
| |
| TEST_F(AudioStreamTest, GapsWillBeFilledWithZeros) { |
| AudioStream stream(""); |
| EXPECT_EQ(base::TimeTicks(), stream.begin_timestamp()); |
| EXPECT_EQ(base::TimeTicks(), stream.end_timestamp()); |
| EXPECT_EQ(0, stream.total_frames()); |
| |
| // Append two buses with a gap between both that is equal to the buffer |
| // duration. |
| // |
| // +----------+ +----------+ |
| // 10ms| | 30ms| | |
| // +----------+ +----------+ |
| // ^ |
| // | |
| // Gap (will be filled with zeros). |
| // |
| ProduceAndAppendAudio(stream, GetTimestamp(base::Milliseconds(10))); |
| ProduceAndAppendAudio(stream, |
| GetTimestamp(stream.end_timestamp().since_origin() + |
| audio_parameters_.GetBufferDuration())); |
| |
| // It's as if a bus filled with zeros was appended in the middle. |
| ASSERT_EQ(base::Milliseconds(10), audio_parameters_.GetBufferDuration()); |
| EXPECT_EQ(GetTimestamp(base::Milliseconds(10)), stream.begin_timestamp()); |
| EXPECT_EQ(GetTimestamp(base::Milliseconds(40)), stream.end_timestamp()); |
| EXPECT_EQ(3 * audio_parameters_.frames_per_buffer(), stream.total_frames()); |
| |
| auto bus = media::AudioBus::Create(audio_parameters_); |
| bus->Zero(); |
| stream.ConsumeAndAccumulateTo(bus.get(), /*destination_start_frame=*/0, |
| audio_parameters_.frames_per_buffer()); |
| // The first one is not zeros. |
| EXPECT_FALSE(bus->AreFramesZero()); |
| // The begin timestamp is pushed after consume, the end timestamp remains the |
| // same. |
| EXPECT_EQ(GetTimestamp(base::Milliseconds(20)), stream.begin_timestamp()); |
| EXPECT_EQ(GetTimestamp(base::Milliseconds(40)), stream.end_timestamp()); |
| |
| // The second one is zero filled. |
| bus->Zero(); |
| stream.ConsumeAndAccumulateTo(bus.get(), /*destination_start_frame=*/0, |
| audio_parameters_.frames_per_buffer()); |
| EXPECT_TRUE(bus->AreFramesZero()); |
| EXPECT_EQ(GetTimestamp(base::Milliseconds(30)), stream.begin_timestamp()); |
| EXPECT_EQ(GetTimestamp(base::Milliseconds(40)), stream.end_timestamp()); |
| |
| // The third one is not zero filled. |
| bus->Zero(); |
| stream.ConsumeAndAccumulateTo(bus.get(), /*destination_start_frame=*/0, |
| audio_parameters_.frames_per_buffer()); |
| EXPECT_FALSE(bus->AreFramesZero()); |
| EXPECT_EQ(GetTimestamp(base::Milliseconds(40)), stream.begin_timestamp()); |
| EXPECT_EQ(GetTimestamp(base::Milliseconds(40)), stream.end_timestamp()); |
| } |
| |
| TEST_F(AudioStreamTest, ConsumeAccumulates) { |
| AudioStream stream(""); |
| auto appended_bus = |
| ProduceAndAppendAudio(stream, GetTimestamp(base::Milliseconds(10))); |
| |
| // The stream has a bus that has the same values as `appended_bus`. When we |
| // consume the stream into `appended_bus`, it's like adding the frames of |
| // `appended_bus` to themselves, effectively doubling the values. |
| auto expected_bus = media::AudioBus::Create(audio_parameters_); |
| appended_bus->CopyTo(expected_bus.get()); |
| expected_bus->Scale(2.0f); |
| |
| stream.ConsumeAndAccumulateTo(appended_bus.get(), |
| /*destination_start_frame=*/0, |
| appended_bus->frames()); |
| |
| EXPECT_TRUE(AreBusesEqual(*expected_bus, *appended_bus)); |
| } |
| |
| TEST_F(AudioStreamTest, PartialConsumes) { |
| AudioStream stream(""); |
| auto appended_bus = |
| ProduceAndAppendAudio(stream, GetTimestamp(base::Milliseconds(10))); |
| |
| const int total_frames = appended_bus->frames(); |
| const int first_segment_frames = 0.3 * total_frames; |
| const int second_segment_frames = total_frames - first_segment_frames; |
| |
| auto expected_first_segment = |
| media::AudioBus::Create(appended_bus->channels(), first_segment_frames); |
| auto expected_second_segment = |
| media::AudioBus::Create(appended_bus->channels(), second_segment_frames); |
| appended_bus->CopyPartialFramesTo(/*source_start_frame=*/0, |
| /*frame_count=*/first_segment_frames, |
| /*dest_start_frame=*/0, |
| /*dest=*/expected_first_segment.get()); |
| appended_bus->CopyPartialFramesTo(/*source_start_frame=*/first_segment_frames, |
| /*frame_count=*/second_segment_frames, |
| /*dest_start_frame=*/0, |
| /*dest=*/expected_second_segment.get()); |
| |
| auto actual_first_segment = |
| media::AudioBus::Create(appended_bus->channels(), first_segment_frames); |
| actual_first_segment->Zero(); |
| auto actual_second_segment = |
| media::AudioBus::Create(appended_bus->channels(), second_segment_frames); |
| actual_second_segment->Zero(); |
| |
| stream.ConsumeAndAccumulateTo(/*destination=*/actual_first_segment.get(), |
| /*destination_start_frame=*/0, |
| /*frames_to_consume=*/first_segment_frames); |
| // After consuming a number of frames from the stream that is equal to the |
| // number of frames in the first segment, the remaining total number of frames |
| // in the stream is equal to the number of frames in the second segment. |
| EXPECT_EQ(second_segment_frames, stream.total_frames()); |
| |
| stream.ConsumeAndAccumulateTo(/*destination=*/actual_second_segment.get(), |
| /*destination_start_frame=*/0, |
| /*frames_to_consume=*/second_segment_frames); |
| EXPECT_TRUE(stream.empty()); |
| |
| EXPECT_TRUE(AreBusesEqual(*expected_first_segment, *actual_first_segment)); |
| EXPECT_TRUE(AreBusesEqual(*expected_second_segment, *actual_second_segment)); |
| } |
| |
| TEST_F(AudioStreamTest, ConsumeToMisalignedDestination) { |
| AudioStream stream(""); |
| auto appended_bus = |
| ProduceAndAppendAudio(stream, GetTimestamp(base::Milliseconds(10))); |
| |
| auto destination = |
| audio_capture_util::CreateStereoZeroInitializedAudioBusForFrames( |
| 2 * appended_bus->frames()); |
| |
| // Find the first misaligned frame, so that we can use it as the start frame |
| // to consume to in the `destination`. |
| int mis_aligned_start_frame = 0; |
| for (; mis_aligned_start_frame < destination->frames(); |
| ++mis_aligned_start_frame) { |
| if (!base::IsAligned(&destination->channel(0)[mis_aligned_start_frame], |
| media::vector_math::kRequiredAlignment)) { |
| break; |
| } |
| } |
| |
| stream.ConsumeAndAccumulateTo( |
| /*destination=*/destination.get(), |
| /*destination_start_frame=*/mis_aligned_start_frame, |
| /*frames_to_consume=*/appended_bus->frames()); |
| EXPECT_TRUE(stream.empty()); |
| |
| // This bus contains the frames we consumed from the stream into destination |
| // starting at the misaligned address. It should be exactly equal to the |
| // `appended_bus`. |
| auto consumed_frames = media::AudioBus::Create(audio_parameters_); |
| destination->CopyPartialFramesTo( |
| /*source_start_frame=*/mis_aligned_start_frame, |
| /*frame_count=*/appended_bus->frames(), |
| /*dest_start_frame=*/0, |
| /*dest=*/consumed_frames.get()); |
| |
| EXPECT_TRUE(AreBusesEqual(*consumed_frames, *appended_bus)); |
| } |
| |
| } // namespace recording |