// Copyright 2017 The Chromium OS 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 <memory> #include <stdint.h> #include <stdio.h> #include <time.h> #include <gtest/gtest.h> extern "C" { #include "dev_io.h" // tested #include "dev_stream.h" // tested #include "cras_rstream.h" // stubbed #include "cras_iodev.h" // stubbed #include "cras_shm.h" #include "cras_types.h" #include "utlist.h" struct audio_thread_event_log* atlog; } #include "dev_io_stubs.h" #include "iodev_stub.h" #include "rstream_stub.h" #define FAKE_POLL_FD 33 namespace { class TimingSuite : public testing::Test{ protected: virtual void SetUp() { atlog = static_cast<audio_thread_event_log*>(calloc(1, sizeof(*atlog))); iodev_stub_reset(); rstream_stub_reset(); } virtual void TearDown() { free(atlog); } timespec SingleInputDevNextWake( size_t dev_cb_threshold, size_t dev_level, const timespec* level_timestamp, cras_audio_format* dev_format, const std::vector<StreamPtr>& streams, CRAS_NODE_TYPE active_node_type = CRAS_NODE_TYPE_MIC) { struct open_dev* dev_list_ = NULL; DevicePtr dev = create_device(CRAS_STREAM_INPUT, dev_cb_threshold, dev_format, active_node_type); dev->dev->input_streaming = true; DL_APPEND(dev_list_, dev->odev.get()); for (auto const& stream : streams) { add_stream_to_dev(dev->dev, stream); } // Set response for frames_queued. iodev_stub_frames_queued(dev->dev.get(), dev_level, *level_timestamp); dev_io_send_captured_samples(dev_list_); struct timespec dev_time; dev_time.tv_sec = level_timestamp->tv_sec + 500; // Far in the future. dev_io_next_input_wake(&dev_list_, &dev_time); return dev_time; } }; // One device, one stream, write a callback of data and check the sleep time is // one more wakeup interval. TEST_F(TimingSuite, WaitAfterFill) { const size_t cb_threshold = 480; cras_audio_format format; fill_audio_format(&format, 48000); StreamPtr stream = create_stream(1, 1, CRAS_STREAM_INPUT, cb_threshold, &format); // rstream's next callback is now and there is enough data to fill. struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); stream->rstream->next_cb_ts = start; AddFakeDataToStream(stream.get(), 480); std::vector<StreamPtr> streams; streams.emplace_back(std::move(stream)); timespec dev_time = SingleInputDevNextWake(cb_threshold, 0, &start, &format, streams); // The next callback should be scheduled 10ms in the future. // And the next wake up should reflect the only attached stream. EXPECT_EQ(dev_time.tv_sec, streams[0]->rstream->next_cb_ts.tv_sec); EXPECT_EQ(dev_time.tv_nsec, streams[0]->rstream->next_cb_ts.tv_nsec); } // One device with one stream which has block_size larger than the device buffer // level. If the device buffer level = 0, the input device wake time should be // set to (buffer_size / 2) / device_rate secs. TEST_F(TimingSuite, LargeCallbackStreamWithEmptyBuffer) { const size_t cb_threshold = 3000; const size_t dev_cb_threshold = 1200; const size_t dev_level = 0; cras_audio_format format; fill_audio_format(&format, 48000); StreamPtr stream = create_stream(1, 1, CRAS_STREAM_INPUT, cb_threshold, &format); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); stream->rstream->next_cb_ts = start; std::vector<StreamPtr> streams; streams.emplace_back(std::move(stream)); timespec dev_time = SingleInputDevNextWake( dev_cb_threshold, dev_level, &start, &format, streams); struct timespec delta; subtract_timespecs(&dev_time, &start, &delta); // The next dev wake ts should be 25ms since the buffer level is empty and // 1200 / 48000 = 0.025. EXPECT_EQ(delta.tv_sec, 0); EXPECT_LT(delta.tv_nsec, 25000000 + 5000 * 1000); EXPECT_GT(delta.tv_nsec, 25000000 - 5000 * 1000); } // One device with one stream which has block_size larger than the device buffer // level. If the device buffer level = buffer_size / 2, the input device wake // time should be set to max(0, 5ms) = 5ms to prevent busy loop occurs. TEST_F(TimingSuite, LargeCallbackStreamWithHalfFullBuffer) { const size_t cb_threshold = 3000; const size_t dev_cb_threshold = 1200; const size_t dev_level = 1200; cras_audio_format format; fill_audio_format(&format, 48000); StreamPtr stream = create_stream(1, 1, CRAS_STREAM_INPUT, cb_threshold, &format); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); stream->rstream->next_cb_ts = start; std::vector<StreamPtr> streams; streams.emplace_back(std::move(stream)); timespec dev_time = SingleInputDevNextWake( dev_cb_threshold, dev_level, &start, &format, streams); struct timespec delta; subtract_timespecs(&dev_time, &start, &delta); // The next dev wake ts should be 5ms since the buffer level is half full. EXPECT_EQ(delta.tv_sec, 0); EXPECT_LT(delta.tv_nsec, 5000000 + 5000 * 1000); EXPECT_GT(delta.tv_nsec, 5000000 - 5000 * 1000); } // One device(48k), one stream(44.1k), write a callback of data and check that // the sleep time is correct when doing SRC. TEST_F(TimingSuite, WaitAfterFillSRC) { cras_audio_format dev_format; fill_audio_format(&dev_format, 48000); cras_audio_format stream_format; fill_audio_format(&stream_format, 44100); StreamPtr stream = create_stream(1, 1, CRAS_STREAM_INPUT, 441, &stream_format); // rstream's next callback is now and there is enough data to fill. struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); stream->rstream->next_cb_ts = start; AddFakeDataToStream(stream.get(), 441); std::vector<StreamPtr> streams; streams.emplace_back(std::move(stream)); timespec dev_time = SingleInputDevNextWake(480, 0, &start, &dev_format, streams); // The next callback should be scheduled 10ms in the future. struct timespec delta; subtract_timespecs(&dev_time, &start, &delta); EXPECT_LT(9900 * 1000, delta.tv_nsec); EXPECT_GT(10100 * 1000, delta.tv_nsec); } // One device, two streams. One stream is ready the other still needs data. // Checks that the sleep interval is based on the time the device will take to // supply the needed samples for stream2. TEST_F(TimingSuite, WaitTwoStreamsSameFormat) { const size_t cb_threshold = 480; cras_audio_format format; fill_audio_format(&format, 48000); // stream1's next callback is now and there is enough data to fill. StreamPtr stream1 = create_stream(1, 1, CRAS_STREAM_INPUT, cb_threshold, &format); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); stream1->rstream->next_cb_ts = start; AddFakeDataToStream(stream1.get(), cb_threshold); // stream2 is only half full. StreamPtr stream2 = create_stream(1, 1, CRAS_STREAM_INPUT, cb_threshold, &format); stream2->rstream->next_cb_ts = start; AddFakeDataToStream(stream2.get(), 240); std::vector<StreamPtr> streams; streams.emplace_back(std::move(stream1)); streams.emplace_back(std::move(stream2)); timespec dev_time = SingleInputDevNextWake(cb_threshold, 0, &start, &format, streams); // Should wait for approximately 5 milliseconds for 240 samples at 48k. struct timespec delta2; subtract_timespecs(&dev_time, &start, &delta2); EXPECT_LT(4900 * 1000, delta2.tv_nsec); EXPECT_GT(5100 * 1000, delta2.tv_nsec); } // One device(44.1), two streams(44.1, 48). One stream is ready the other still // needs data. Checks that the sleep interval is based on the time the device // will take to supply the needed samples for stream2, stream2 is sample rate // converted from the 44.1k device to the 48k stream. TEST_F(TimingSuite, WaitTwoStreamsDifferentRates) { cras_audio_format s1_format, s2_format; fill_audio_format(&s1_format, 44100); fill_audio_format(&s2_format, 48000); // stream1's next callback is now and there is enough data to fill. StreamPtr stream1 = create_stream(1, 1, CRAS_STREAM_INPUT, 441, &s1_format); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); stream1->rstream->next_cb_ts = start; AddFakeDataToStream(stream1.get(), 441); // stream2's next callback is now but there is only half a callback of data. StreamPtr stream2 = create_stream(1, 1, CRAS_STREAM_INPUT, 480, &s2_format); stream2->rstream->next_cb_ts = start; AddFakeDataToStream(stream2.get(), 240); std::vector<StreamPtr> streams; streams.emplace_back(std::move(stream1)); streams.emplace_back(std::move(stream2)); timespec dev_time = SingleInputDevNextWake(441, 0, &start, &s1_format, streams); // Should wait for approximately 5 milliseconds for 240 48k samples from the // 44.1k device. struct timespec delta2; subtract_timespecs(&dev_time, &start, &delta2); EXPECT_LT(4900 * 1000, delta2.tv_nsec); EXPECT_GT(5100 * 1000, delta2.tv_nsec); } // One device, two streams. Both streams get a full callback of data and the // device has enough samples for the next callback already. Checks that the // shorter of the two streams times is used for the next sleep interval. TEST_F(TimingSuite, WaitTwoStreamsDifferentWakeupTimes) { cras_audio_format s1_format, s2_format; fill_audio_format(&s1_format, 44100); fill_audio_format(&s2_format, 48000); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); // stream1's next callback is in 3ms. StreamPtr stream1 = create_stream(1, 1, CRAS_STREAM_INPUT, 441, &s1_format); stream1->rstream->next_cb_ts = start; const timespec three_millis = { 0, 3 * 1000 * 1000 }; add_timespecs(&stream1->rstream->next_cb_ts, &three_millis); AddFakeDataToStream(stream1.get(), 441); // stream2 is also ready next cb in 5ms.. StreamPtr stream2 = create_stream(1, 1, CRAS_STREAM_INPUT, 480, &s2_format); stream2->rstream->next_cb_ts = start; const timespec five_millis = { 0, 5 * 1000 * 1000 }; add_timespecs(&stream2->rstream->next_cb_ts, &five_millis); AddFakeDataToStream(stream1.get(), 480); std::vector<StreamPtr> streams; streams.emplace_back(std::move(stream1)); streams.emplace_back(std::move(stream2)); timespec dev_time = SingleInputDevNextWake(441, 441, &start, &s1_format, streams); // Should wait for approximately 3 milliseconds for stream 1 first. struct timespec delta2; subtract_timespecs(&dev_time, &start, &delta2); EXPECT_LT(2900 * 1000, delta2.tv_nsec); EXPECT_GT(3100 * 1000, delta2.tv_nsec); } // One hotword stream attaches to hotword device. Input data has copied from // device to stream but total number is less than cb_threshold. Hotword stream // should be scheduled wake base on the samples needed to fill full shm. TEST_F(TimingSuite, HotwordStreamUseDevTiming) { cras_audio_format fmt; fill_audio_format(&fmt, 48000); struct timespec start, delay; clock_gettime(CLOCK_MONOTONIC_RAW, &start); StreamPtr stream = create_stream(1, 1, CRAS_STREAM_INPUT, 240, &fmt); stream->rstream->flags = HOTWORD_STREAM; stream->rstream->next_cb_ts = start; delay.tv_sec = 0; delay.tv_nsec = 3 * 1000 * 1000; add_timespecs(&stream->rstream->next_cb_ts, &delay); // Add fake data to stream and device so its slightly less than cb_threshold. // Expect to wait for samples to fill the full buffer (480 - 192) frames // instead of using the next_cb_ts. AddFakeDataToStream(stream.get(), 192); std::vector<StreamPtr> streams; streams.emplace_back(std::move(stream)); timespec dev_time = SingleInputDevNextWake(4096, 0, &start, &fmt, streams); struct timespec delta; subtract_timespecs(&dev_time, &start, &delta); // 288 frames worth of time = 6 ms. EXPECT_EQ(6 * 1000 * 1000, delta.tv_nsec); } // One hotword stream attaches to hotword device. Input data burst to a number // larger than cb_threshold. Also, stream is pending client reply. // In this case stream fd is used to poll for next wake. // And the dev wake time is unchanged from the default 20 seconds limit. TEST_F(TimingSuite, HotwordStreamBulkDataIsPending) { int poll_fd = 0; cras_audio_format fmt; fill_audio_format(&fmt, 48000); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); StreamPtr stream = create_stream(1, 1, CRAS_STREAM_INPUT, 240, &fmt); stream->rstream->flags = HOTWORD_STREAM; stream->rstream->next_cb_ts = start; AddFakeDataToStream(stream.get(), 480); std::vector<StreamPtr> streams; streams.emplace_back(std::move(stream)); // Stream is pending the reply from client. rstream_stub_pending_reply(streams[0]->rstream.get(), 1); // There is more than 1 cb_threshold of data in device. timespec dev_time = SingleInputDevNextWake( 4096, 7000, &start, &fmt, streams, CRAS_NODE_TYPE_HOTWORD); // Need to wait for stream fd in the next ppoll. poll_fd = dev_stream_poll_stream_fd(streams[0]->dstream.get()); EXPECT_EQ(FAKE_POLL_FD, poll_fd); struct timespec delta; subtract_timespecs(&dev_time, &start, &delta); // Wake up time should be default 20 seconds because audio thread // depends on reply from client to wake it up. EXPECT_LT(19, delta.tv_sec); EXPECT_GT(21, delta.tv_sec); } // One hotword stream attaches to hotword device. Input data burst to a number // larger than cb_threshold. However, stream is not pending client reply. // This happens if there was no data during capture_to_stream. // In this case stream fd is NOT used to poll for next wake. // And the dev wake time is changed to a 0 instead of default 20 seconds. TEST_F(TimingSuite, HotwordStreamBulkDataIsNotPending) { int poll_fd = 0; cras_audio_format fmt; fill_audio_format(&fmt, 48000); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); StreamPtr stream = create_stream(1, 1, CRAS_STREAM_INPUT, 240, &fmt); stream->rstream->flags = HOTWORD_STREAM; stream->rstream->next_cb_ts = start; AddFakeDataToStream(stream.get(), 480); std::vector<StreamPtr> streams; streams.emplace_back(std::move(stream)); // Stream is not pending the reply from client. rstream_stub_pending_reply(streams[0]->rstream.get(), 0); // There is more than 1 cb_threshold of data in device. timespec dev_time = SingleInputDevNextWake(4096, 7000, &start, &fmt, streams); // Does not need to wait for stream fd in the next ppoll. poll_fd = dev_stream_poll_stream_fd(streams[0]->dstream.get()); EXPECT_EQ(-1, poll_fd); struct timespec delta; subtract_timespecs(&dev_time, &start, &delta); // Wake up time should be very small because there is enough // data to be send to client. EXPECT_LT(delta.tv_sec, 0.1); } /* Stubs */ extern "C" { int cras_server_metrics_highest_hw_level(unsigned hw_level, enum CRAS_STREAM_DIRECTION direction) { return 0; } int cras_server_metrics_longest_fetch_delay(unsigned delay_msec) { return 0; } int cras_server_metrics_num_underruns(unsigned num_underruns) { return 0; } int input_data_get_for_stream( struct input_data *data, struct cras_rstream *stream, struct buffer_share *offsets, struct cras_audio_area **area, unsigned int *offset) { return 0; } int input_data_put_for_stream(struct input_data *data, struct cras_rstream *stream, struct buffer_share *offsets, unsigned int frames) { return 0; } struct cras_audio_format *cras_rstream_post_processing_format( const struct cras_rstream *stream, void *dev_ptr) { return NULL; } } // extern "C" } // namespace int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }