// 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 #include #include #include #include #include extern "C" { #include "cras_iodev.h" // stubbed #include "cras_rstream.h" // stubbed #include "cras_shm.h" #include "cras_types.h" #include "dev_io.h" // tested #include "dev_stream.h" // tested #include "utlist.h" struct audio_thread_event_log* atlog; } #include "dev_io_stubs.h" #include "iodev_stub.h" #include "metrics_stub.h" #include "rstream_stub.h" #define FAKE_POLL_FD 33 namespace { class TimingSuite : public testing::Test { protected: virtual void SetUp() { atlog = static_cast(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& 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; } timespec SingleOutputDevNextWake( size_t dev_cb_threshold, size_t dev_level, const timespec* level_timestamp, cras_audio_format* dev_format, const std::vector& streams, const timespec* dev_wake_ts, CRAS_NODE_TYPE active_node_type = CRAS_NODE_TYPE_HEADPHONE) { struct open_dev* dev_list_ = NULL; DevicePtr dev = create_device(CRAS_STREAM_OUTPUT, dev_cb_threshold, dev_format, active_node_type); DL_APPEND(dev_list_, dev->odev.get()); for (auto const& stream : streams) { add_stream_to_dev(dev->dev, stream); } dev->odev->wake_ts = *dev_wake_ts; // Set response for frames_queued. iodev_stub_frames_queued(dev->dev.get(), dev_level, *level_timestamp); struct timespec dev_time; dev_time.tv_sec = level_timestamp->tv_sec + 500; // Far in the future. dev_io_next_output_wake(&dev_list_, &dev_time); return dev_time; } }; extern "C" { // From librt. Fix now at this time. int clock_gettime(clockid_t clk_id, struct timespec* tp) { tp->tv_sec = 12345; tp->tv_nsec = 987654321; return 0; } }; // Add a new input stream, make sure the initial next_cb_ts is 0. TEST_F(TimingSuite, NewInputStreamInit) { struct open_dev* odev_list_ = NULL; struct open_dev* idev_list_ = NULL; cras_audio_format format; fill_audio_format(&format, 48000); DevicePtr dev = create_device(CRAS_STREAM_INPUT, 1024, &format, CRAS_NODE_TYPE_MIC); DL_APPEND(idev_list_, dev->odev.get()); struct cras_iodev* iodev = dev->odev->dev; ShmPtr shm = create_shm(480); RstreamPtr rstream = create_rstream(1, CRAS_STREAM_INPUT, 480, &format, shm.get()); dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &iodev, 1); EXPECT_EQ(0, rstream->next_cb_ts.tv_sec); EXPECT_EQ(0, rstream->next_cb_ts.tv_nsec); dev_stream_destroy(iodev->streams); } // There is the pseudo code about wake up time for an input device. // // function set_input_dev_wake_ts(dev): // wake_ts = now + 20s #rule_1 // // cap_limit = MIN(dev_stream_capture_avail(stream)) for stream on dev // // for stream in dev: // wake_ts = MIN(get_input_wake_time(stream, cap_limit), wake_ts) // for stream on dev #rule_2 // if cap_limit: // wake_ts = MIN(get_input_dev_max_wake_ts(dev), wake_ts) #rule_3 // // device.wake_ts = wake_ts // // function get_input_wake_time(stream, cap_limit): // needed_frames_from_device = dev_stream_capture_avail(stream) // // if needed_frames_from_device > cap_limit: #rule_4 // return None // // if stream is USE_DEV_TIMING and stream is pending reply: #rule_5 // return None // // time_for_sample = The time when device gets enough samples #rule_6 // // wake_time_out = MAX(stream.next_cb_ts, time_for_sample) #rule_7 // // if stream is USE_DEV_TIMING: // wake_time_out = time_for_sample #rule_8 // // return wake_time_out // // function get_input_dev_max_wake_ts(dev): // return MAX(5ms, The time when hw_level = buffer_size / 2) #rule_9 // // // dev_stream_capture_avail: The number of frames free to be written to in a // capture stream. // // The following unittests will check these logics. // Test rule_1. // The device wake up time should be 20s from now. TEST_F(TimingSuite, InputWakeTimeNoStreamWithBigBufferDevice) { cras_audio_format format; fill_audio_format(&format, 48000); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); std::vector streams; timespec dev_time = SingleInputDevNextWake(4800000, 0, &start, &format, streams); const timespec add_millis = {20, 0}; add_timespecs(&start, &add_millis); EXPECT_EQ(start.tv_sec, dev_time.tv_sec); EXPECT_EQ(start.tv_nsec, dev_time.tv_nsec); } // Test rule_2, rule_4(Stream 1), rule_7(Stream 2) // Stream 1: next_cb_ts = now, cb_threshold = 480, dev_offset = 0 // Stream 2: next_cb_ts = now + 5s, cb_threshold = 480, dev_offset = 200 // Stream 1 need 480 frames and Stream 2 need 240 frames. So 240 will be the // cap_limit and Stream 1 will be ignored. The next wake up time should be // the next_cb_ts of stream2. TEST_F(TimingSuite, InputWakeTimeTwoStreamsWithFramesInside) { cras_audio_format format; fill_audio_format(&format, 48000); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); StreamPtr stream1 = create_stream(1, 1, CRAS_STREAM_INPUT, 480, &format); stream1->rstream->next_cb_ts = start; StreamPtr stream2 = create_stream(1, 2, CRAS_STREAM_INPUT, 480, &format); stream2->rstream->next_cb_ts = start; stream2->rstream->next_cb_ts.tv_sec += 5; rstream_stub_dev_offset(stream2->rstream.get(), 1, 200); std::vector streams; streams.emplace_back(std::move(stream1)); streams.emplace_back(std::move(stream2)); timespec dev_time = SingleInputDevNextWake(480000, 0, &start, &format, streams); EXPECT_EQ(start.tv_sec + 5, dev_time.tv_sec); EXPECT_EQ(start.tv_nsec, dev_time.tv_nsec); } // Test rule_2, rule_7 // Stream 1: next_cb_ts = now + 2s, cb_threshold = 480, dev_offset = 0 // Stream 2: next_cb_ts = now + 5s, cb_threshold = 480, dev_offset = 0 // The audio thread will choose the earliest next_cb_ts because the they have // the same value of needed_frames_from_device. The next wake up time should // be the next_cb_ts of stream1. TEST_F(TimingSuite, InputWakeTimeTwoEmptyStreams) { cras_audio_format format; fill_audio_format(&format, 48000); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); StreamPtr stream1 = create_stream(1, 1, CRAS_STREAM_INPUT, 480, &format); stream1->rstream->next_cb_ts = start; stream1->rstream->next_cb_ts.tv_sec += 2; StreamPtr stream2 = create_stream(1, 2, CRAS_STREAM_INPUT, 480, &format); stream2->rstream->next_cb_ts = start; stream2->rstream->next_cb_ts.tv_sec += 5; std::vector streams; streams.emplace_back(std::move(stream1)); streams.emplace_back(std::move(stream2)); timespec dev_time = SingleInputDevNextWake(480000, 0, &start, &format, streams); EXPECT_EQ(start.tv_sec + 2, dev_time.tv_sec); EXPECT_EQ(start.tv_nsec, dev_time.tv_nsec); } // Test rule_3. // If cap_limit is zero from stream, input_dev_max_wake_ts should not // be taken into account. TEST_F(TimingSuite, InputWakeTimeOneFullStreamWithDeviceWakeUp) { cras_audio_format format; fill_audio_format(&format, 48000); StreamPtr stream = create_stream(1, 1, CRAS_STREAM_INPUT, 480, &format); struct timespec start, stream_wake; clock_gettime(CLOCK_MONOTONIC_RAW, &start); // Set the next stream wake to be 10ms from now. const timespec ten_millis = {0, 10 * 1000 * 1000}; stream_wake = start; add_timespecs(&stream_wake, &ten_millis); stream->rstream->next_cb_ts = stream_wake; // Add fake data so the stream has no room for more data. AddFakeDataToStream(stream.get(), 480); std::vector streams; streams.emplace_back(std::move(stream)); timespec wake_time = SingleInputDevNextWake(240, 0, &start, &format, streams); // Input device would wake at 5ms from now, but since stream cap_limit == 0 // the final wake_time is determined by stream. EXPECT_EQ(stream_wake.tv_sec, wake_time.tv_sec); EXPECT_EQ(stream_wake.tv_nsec, wake_time.tv_nsec); } // Test rule_3 and rule_9. // One empty stream with small device buffer. It should wake up when there are // buffer_size / 2 frames in device buffer. TEST_F(TimingSuite, InputWakeTimeOneStreamWithDeviceWakeUp) { cras_audio_format format; fill_audio_format(&format, 48000); StreamPtr stream = create_stream(1, 1, CRAS_STREAM_INPUT, 480, &format); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); // The next callback of the new stream is 0. stream->rstream->next_cb_ts.tv_sec = 0; stream->rstream->next_cb_ts.tv_nsec = 0; std::vector streams; streams.emplace_back(std::move(stream)); timespec dev_time = SingleInputDevNextWake(240, 0, &start, &format, streams); // The device wake up time should be 5ms from now. At that time there are // 240 frames in the device. const timespec add_millis = {0, 5 * 1000 * 1000}; add_timespecs(&start, &add_millis); EXPECT_EQ(start.tv_sec, dev_time.tv_sec); EXPECT_EQ(start.tv_nsec, dev_time.tv_nsec); } // Test rule_5. // The stream with USE_DEV_TIMING flag will be ignore if it is pending reply. TEST_F(TimingSuite, InputWakeTimeOneStreamUsingDevTimingWithPendingReply) { cras_audio_format format; fill_audio_format(&format, 48000); StreamPtr stream = create_stream(1, 1, CRAS_STREAM_INPUT, 480, &format); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); // The next callback should be ignored. stream->rstream->next_cb_ts = start; stream->rstream->next_cb_ts.tv_sec += 10; stream->rstream->flags = USE_DEV_TIMING; rstream_stub_pending_reply(stream->rstream.get(), 1); std::vector streams; streams.emplace_back(std::move(stream)); timespec dev_time = SingleInputDevNextWake(4800, 0, &start, &format, streams); // The device wake up time should be 100ms from now. At that time the hw_level // is buffer_size / 2. const timespec add_millis = {0, 100 * 1000 * 1000}; add_timespecs(&start, &add_millis); EXPECT_EQ(start.tv_sec, dev_time.tv_sec); EXPECT_EQ(start.tv_nsec, dev_time.tv_nsec); } // Test rule_6. // Add a new stream, the wake up time is the time when it has enough data to // post. TEST_F(TimingSuite, InputWakeTimeOneStreamWithEmptyDevice) { cras_audio_format format; fill_audio_format(&format, 48000); StreamPtr stream = create_stream(1, 1, CRAS_STREAM_INPUT, 480, &format); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); // The next callback of the new stream is 0. stream->rstream->next_cb_ts.tv_sec = 0; stream->rstream->next_cb_ts.tv_nsec = 0; std::vector streams; streams.emplace_back(std::move(stream)); timespec dev_time = SingleInputDevNextWake(600, 0, &start, &format, streams); // The device wake up time should be 10ms from now. At that time the // stream will have 480 samples to post. const timespec ten_millis = {0, 10 * 1000 * 1000}; add_timespecs(&start, &ten_millis); EXPECT_EQ(0, streams[0]->rstream->next_cb_ts.tv_sec); EXPECT_EQ(0, streams[0]->rstream->next_cb_ts.tv_nsec); EXPECT_EQ(start.tv_sec, dev_time.tv_sec); EXPECT_EQ(start.tv_nsec, dev_time.tv_nsec); } // Test rule_6. // Add a new stream with enough frames in device, check the wake up time is // right now. TEST_F(TimingSuite, InputWakeTimeOneStreamWithFullDevice) { cras_audio_format format; fill_audio_format(&format, 48000); StreamPtr stream = create_stream(1, 1, CRAS_STREAM_INPUT, 480, &format); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); // The next callback of the new stream is 0. stream->rstream->next_cb_ts.tv_sec = 0; stream->rstream->next_cb_ts.tv_nsec = 0; // If there are enough frames in the device, we should wake up immediately. std::vector streams; streams.emplace_back(std::move(stream)); timespec dev_time = SingleInputDevNextWake(480, 480, &start, &format, streams); EXPECT_EQ(0, streams[0]->rstream->next_cb_ts.tv_sec); EXPECT_EQ(0, streams[0]->rstream->next_cb_ts.tv_nsec); EXPECT_EQ(start.tv_sec, dev_time.tv_sec); EXPECT_EQ(start.tv_nsec, dev_time.tv_nsec); } // Test rule_8. // The stream with USE_DEV_TIMING flag should wake up when it has enough frames // to post. TEST_F(TimingSuite, InputWakeTimeOneStreamUsingDevTiming) { cras_audio_format format; fill_audio_format(&format, 48000); StreamPtr stream = create_stream(1, 1, CRAS_STREAM_INPUT, 480, &format); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); // The next callback should be ignored. stream->rstream->next_cb_ts = start; stream->rstream->next_cb_ts.tv_sec += 10; stream->rstream->flags = USE_DEV_TIMING; std::vector streams; streams.emplace_back(std::move(stream)); timespec dev_time = SingleInputDevNextWake(600, 0, &start, &format, streams); // The device wake up time should be 10ms from now. At that time the // stream will have 480 samples to post. const timespec add_millis = {0, 10 * 1000 * 1000}; add_timespecs(&start, &add_millis); EXPECT_EQ(start.tv_sec, dev_time.tv_sec); EXPECT_EQ(start.tv_nsec, dev_time.tv_nsec); } // Test rule_9. // The device wake up time should be 10ms from now. At that time the hw_level // is buffer_size / 2. TEST_F(TimingSuite, InputWakeTimeNoStreamSmallBufferDevice) { cras_audio_format format; fill_audio_format(&format, 48000); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); std::vector streams; timespec dev_time = SingleInputDevNextWake(480, 0, &start, &format, streams); const timespec add_millis = {0, 10 * 1000 * 1000}; add_timespecs(&start, &add_millis); EXPECT_EQ(start.tv_sec, dev_time.tv_sec); EXPECT_EQ(start.tv_nsec, dev_time.tv_nsec); } // Test rule_9. // There are more than buffer_size / 2 frames in the device. The device needs // to sleep at least 5ms. TEST_F(TimingSuite, InputWakeTimeOneStreamWithEnoughFramesInDevice) { cras_audio_format format; fill_audio_format(&format, 48000); StreamPtr stream = create_stream(1, 1, CRAS_STREAM_INPUT, 480, &format); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); // Make next_cb_ts far from now. stream->rstream->next_cb_ts = start; stream->rstream->next_cb_ts.tv_sec += 10; std::vector streams; streams.emplace_back(std::move(stream)); timespec dev_time = SingleInputDevNextWake(480, 480, &start, &format, streams); const timespec add_millis = {0, 5 * 1000 * 1000}; add_timespecs(&start, &add_millis); EXPECT_EQ(start.tv_sec, dev_time.tv_sec); EXPECT_EQ(start.tv_nsec, dev_time.tv_nsec); } // 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 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. const timespec ten_millis = {0, 10 * 1000 * 1000}; add_timespecs(&start, &ten_millis); EXPECT_EQ(start.tv_sec, streams[0]->rstream->next_cb_ts.tv_sec); EXPECT_EQ(start.tv_nsec, streams[0]->rstream->next_cb_ts.tv_nsec); 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 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 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 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 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 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 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 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 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 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); } // When a new output stream is added, there are two rules to determine the // initial next_cb_ts. // 1. If there is a matched input stream, use the next_cb_ts and // sleep_interval_ts from that input stream as the initial values. // 2. If the device already has streams, the next_cb_ts will be the earliest // next callback time from these streams. // 3. If there are no other streams, the next_cb_ts will be set to the time // when the valid frames in device is lower than cb_threshold. (If it is // already lower than cb_threshold, set next_cb_ts to now.) // Test rule 1. // There is a matched input stream. The next_cb_ts of the newly added output // stream will use the next_cb_ts from the input stream. TEST_F(TimingSuite, NewOutputStreamInitExistMatchedStream) { struct open_dev* odev_list_ = NULL; struct open_dev* idev_list_ = NULL; cras_audio_format format; fill_audio_format(&format, 48000); DevicePtr out_dev = create_device(CRAS_STREAM_OUTPUT, 1024, &format, CRAS_NODE_TYPE_HEADPHONE); DL_APPEND(odev_list_, out_dev->odev.get()); struct cras_iodev* out_iodev = out_dev->odev->dev; DevicePtr in_dev = create_device(CRAS_STREAM_INPUT, 1024, &format, CRAS_NODE_TYPE_MIC); DL_APPEND(idev_list_, in_dev->odev.get()); StreamPtr in_stream = create_stream(1, 1, CRAS_STREAM_INPUT, 480, &format); add_stream_to_dev(in_dev->dev, in_stream); in_stream->rstream->next_cb_ts.tv_sec = 54321; in_stream->rstream->next_cb_ts.tv_nsec = 12345; in_stream->rstream->sleep_interval_ts.tv_sec = 321; in_stream->rstream->sleep_interval_ts.tv_nsec = 123; ShmPtr shm = create_shm(480); RstreamPtr rstream = create_rstream(1, CRAS_STREAM_OUTPUT, 480, &format, shm.get()); dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &out_iodev, 1); EXPECT_EQ(in_stream->rstream->next_cb_ts.tv_sec, rstream->next_cb_ts.tv_sec); EXPECT_EQ(in_stream->rstream->next_cb_ts.tv_nsec, rstream->next_cb_ts.tv_nsec); EXPECT_EQ(in_stream->rstream->sleep_interval_ts.tv_sec, rstream->sleep_interval_ts.tv_sec); EXPECT_EQ(in_stream->rstream->sleep_interval_ts.tv_nsec, rstream->sleep_interval_ts.tv_nsec); dev_stream_destroy(out_iodev->streams); } // Test rule 2. // The device already has streams, the next_cb_ts will be the earliest // next_cb_ts from these streams. TEST_F(TimingSuite, NewOutputStreamInitStreamInDevice) { struct open_dev* odev_list_ = NULL; struct open_dev* idev_list_ = NULL; cras_audio_format format; fill_audio_format(&format, 48000); DevicePtr dev = create_device(CRAS_STREAM_OUTPUT, 1024, &format, CRAS_NODE_TYPE_HEADPHONE); DL_APPEND(odev_list_, dev->odev.get()); struct cras_iodev* iodev = dev->odev->dev; StreamPtr stream = create_stream(1, 1, CRAS_STREAM_OUTPUT, 480, &format); add_stream_to_dev(dev->dev, stream); stream->rstream->next_cb_ts.tv_sec = 54321; stream->rstream->next_cb_ts.tv_nsec = 12345; ShmPtr shm = create_shm(480); RstreamPtr rstream = create_rstream(1, CRAS_STREAM_OUTPUT, 480, &format, shm.get()); dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &iodev, 1); EXPECT_EQ(stream->rstream->next_cb_ts.tv_sec, rstream->next_cb_ts.tv_sec); EXPECT_EQ(stream->rstream->next_cb_ts.tv_nsec, rstream->next_cb_ts.tv_nsec); dev_stream_destroy(iodev->streams->next); } // Test rule 3. // The there are no streams and no frames in device buffer. The next_cb_ts // will be set to now. TEST_F(TimingSuite, NewOutputStreamInitNoStreamNoFramesInDevice) { struct open_dev* odev_list_ = NULL; struct open_dev* idev_list_ = NULL; cras_audio_format format; fill_audio_format(&format, 48000); DevicePtr dev = create_device(CRAS_STREAM_OUTPUT, 1024, &format, CRAS_NODE_TYPE_HEADPHONE); DL_APPEND(odev_list_, dev->odev.get()); struct cras_iodev* iodev = dev->odev->dev; struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); ShmPtr shm = create_shm(480); RstreamPtr rstream = create_rstream(1, CRAS_STREAM_OUTPUT, 480, &format, shm.get()); dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &iodev, 1); EXPECT_EQ(start.tv_sec, rstream->next_cb_ts.tv_sec); EXPECT_EQ(start.tv_nsec, rstream->next_cb_ts.tv_nsec); dev_stream_destroy(iodev->streams); } // Test rule 2. // The there are no streams and some valid frames in device buffer. The // next_cb_ts will be set to the time that valid frames in device is lower // than cb_threshold. TEST_F(TimingSuite, NewOutputStreamInitNoStreamSomeFramesInDevice) { struct open_dev* odev_list_ = NULL; struct open_dev* idev_list_ = NULL; cras_audio_format format; fill_audio_format(&format, 48000); DevicePtr dev = create_device(CRAS_STREAM_OUTPUT, 1024, &format, CRAS_NODE_TYPE_HEADPHONE); DL_APPEND(odev_list_, dev->odev.get()); struct cras_iodev* iodev = dev->odev->dev; struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); iodev_stub_valid_frames(iodev, 960, start); ShmPtr shm = create_shm(480); RstreamPtr rstream = create_rstream(1, CRAS_STREAM_OUTPUT, 480, &format, shm.get()); dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &iodev, 1); // The next_cb_ts should be 10ms from now. At that time there are // only 480 valid frames in the device. const timespec add_millis = {0, 10 * 1000 * 1000}; add_timespecs(&start, &add_millis); EXPECT_EQ(start.tv_sec, rstream->next_cb_ts.tv_sec); EXPECT_EQ(start.tv_nsec, rstream->next_cb_ts.tv_nsec); dev_stream_destroy(iodev->streams); } // There is the pseudo code about wake up time for a output device. // // function dev_io_next_output_wake(dev): // wake_ts = get_next_stream_wake_from_list(dev.streams) // if cras_iodev_odev_should_wake(dev): // wake_ts = MIN(wake_ts, dev.wake_ts) # rule_1 // // function get_next_stream_wake_from_list(streams): // for stream in streams: // if stream is draining: # rule_2 // continue // if stream is pending reply: # rule_3 // continue // if stream is USE_DEV_TIMING: # rule_4 // continue // min_ts = MIN(min_ts, stream.next_cb_ts) # rule_5 // return min_ts // // # This function is in iodev so we don't test its logic here. // function cras_iodev_odev_should_wake(dev): // if dev.is_free_running: // return False // if dev.state == NORMAL_RUN or dev.state == NO_STREAM_RUN: // return True // return False // Test rule_1. // The wake up time should be the earlier time amoung streams and devices. TEST_F(TimingSuite, OutputWakeTimeOneStreamWithEarlierStreamWakeTime) { cras_audio_format format; fill_audio_format(&format, 48000); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); StreamPtr stream = create_stream(1, 1, CRAS_STREAM_OUTPUT, 480, &format); stream->rstream->next_cb_ts = start; stream->rstream->next_cb_ts.tv_sec += 1; std::vector streams; streams.emplace_back(std::move(stream)); struct timespec dev_wake_ts = start; dev_wake_ts.tv_sec += 2; timespec dev_time = SingleOutputDevNextWake(48000, 0, &start, &format, streams, &dev_wake_ts); EXPECT_EQ(start.tv_sec + 1, dev_time.tv_sec); EXPECT_EQ(start.tv_nsec, dev_time.tv_nsec); } // Test rule_1. // The wake up time should be the earlier time amoung streams and devices. TEST_F(TimingSuite, OutputWakeTimeOneStreamWithEarlierDeviceWakeTime) { cras_audio_format format; fill_audio_format(&format, 48000); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); StreamPtr stream = create_stream(1, 1, CRAS_STREAM_OUTPUT, 480, &format); stream->rstream->next_cb_ts = start; stream->rstream->next_cb_ts.tv_sec += 2; std::vector streams; streams.emplace_back(std::move(stream)); struct timespec dev_wake_ts = start; dev_wake_ts.tv_sec += 1; timespec dev_time = SingleOutputDevNextWake(48000, 0, &start, &format, streams, &dev_wake_ts); EXPECT_EQ(start.tv_sec + 1, dev_time.tv_sec); EXPECT_EQ(start.tv_nsec, dev_time.tv_nsec); } // Test rule_2. // The stream 1 is draining so it will be ignored. The wake up time should be // the next_cb_ts of stream 2. TEST_F(TimingSuite, OutputWakeTimeTwoStreamsWithOneIsDraining) { cras_audio_format format; fill_audio_format(&format, 48000); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); StreamPtr stream1 = create_stream(1, 1, CRAS_STREAM_OUTPUT, 480, &format); stream1->rstream->next_cb_ts = start; stream1->rstream->next_cb_ts.tv_sec += 2; stream1->rstream->is_draining = 1; stream1->rstream->queued_frames = 480; StreamPtr stream2 = create_stream(1, 2, CRAS_STREAM_OUTPUT, 480, &format); stream2->rstream->next_cb_ts = start; stream2->rstream->next_cb_ts.tv_sec += 5; std::vector streams; streams.emplace_back(std::move(stream1)); streams.emplace_back(std::move(stream2)); struct timespec dev_wake_ts = start; dev_wake_ts.tv_sec += 10; timespec dev_time = SingleOutputDevNextWake(48000, 0, &start, &format, streams, &dev_wake_ts); EXPECT_EQ(start.tv_sec + 5, dev_time.tv_sec); EXPECT_EQ(start.tv_nsec, dev_time.tv_nsec); } // Test rule_3. // The stream 1 is pending reply so it will be ignored. The wake up time should // be the next_cb_ts of stream 2. TEST_F(TimingSuite, OutputWakeTimeTwoStreamsWithOneIsPendingReply) { cras_audio_format format; fill_audio_format(&format, 48000); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); StreamPtr stream1 = create_stream(1, 1, CRAS_STREAM_OUTPUT, 480, &format); stream1->rstream->next_cb_ts = start; stream1->rstream->next_cb_ts.tv_sec += 2; rstream_stub_pending_reply(stream1->rstream.get(), 1); StreamPtr stream2 = create_stream(1, 2, CRAS_STREAM_OUTPUT, 480, &format); stream2->rstream->next_cb_ts = start; stream2->rstream->next_cb_ts.tv_sec += 5; std::vector streams; streams.emplace_back(std::move(stream1)); streams.emplace_back(std::move(stream2)); struct timespec dev_wake_ts = start; dev_wake_ts.tv_sec += 10; timespec dev_time = SingleOutputDevNextWake(48000, 0, &start, &format, streams, &dev_wake_ts); EXPECT_EQ(start.tv_sec + 5, dev_time.tv_sec); EXPECT_EQ(start.tv_nsec, dev_time.tv_nsec); } // Test rule_4. // The stream 1 is uning device timing so it will be ignored. The wake up time // should be the next_cb_ts of stream 2. TEST_F(TimingSuite, OutputWakeTimeTwoStreamsWithOneIsUsingDevTiming) { cras_audio_format format; fill_audio_format(&format, 48000); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); StreamPtr stream1 = create_stream(1, 1, CRAS_STREAM_OUTPUT, 480, &format); stream1->rstream->next_cb_ts = start; stream1->rstream->next_cb_ts.tv_sec += 2; stream1->rstream->flags = USE_DEV_TIMING; StreamPtr stream2 = create_stream(1, 2, CRAS_STREAM_OUTPUT, 480, &format); stream2->rstream->next_cb_ts = start; stream2->rstream->next_cb_ts.tv_sec += 5; std::vector streams; streams.emplace_back(std::move(stream1)); streams.emplace_back(std::move(stream2)); struct timespec dev_wake_ts = start; dev_wake_ts.tv_sec += 10; timespec dev_time = SingleOutputDevNextWake(48000, 0, &start, &format, streams, &dev_wake_ts); EXPECT_EQ(start.tv_sec + 5, dev_time.tv_sec); EXPECT_EQ(start.tv_nsec, dev_time.tv_nsec); } // Test rule_5. // The wake up time should be the next_cb_ts of streams. TEST_F(TimingSuite, OutputWakeTimeTwoStreams) { cras_audio_format format; fill_audio_format(&format, 48000); struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); StreamPtr stream1 = create_stream(1, 1, CRAS_STREAM_OUTPUT, 480, &format); stream1->rstream->next_cb_ts = start; stream1->rstream->next_cb_ts.tv_sec += 2; StreamPtr stream2 = create_stream(1, 2, CRAS_STREAM_OUTPUT, 480, &format); stream2->rstream->next_cb_ts = start; stream2->rstream->next_cb_ts.tv_sec += 5; std::vector streams; streams.emplace_back(std::move(stream1)); streams.emplace_back(std::move(stream2)); struct timespec dev_wake_ts = start; dev_wake_ts.tv_sec += 10; timespec dev_time = SingleOutputDevNextWake(48000, 0, &start, &format, streams, &dev_wake_ts); EXPECT_EQ(start.tv_sec + 2, dev_time.tv_sec); EXPECT_EQ(start.tv_nsec, dev_time.tv_nsec); } // One device, one stream, fetch stream and check the sleep time is one more // wakeup interval. TEST_F(TimingSuite, OutputStreamsUpdateAfterFetching) { cras_audio_format format; fill_audio_format(&format, 48000); StreamPtr stream = create_stream(1, 1, CRAS_STREAM_OUTPUT, 480, &format); // rstream's next callback is now. struct timespec start; clock_gettime(CLOCK_MONOTONIC_RAW, &start); stream->rstream->next_cb_ts = start; struct open_dev* dev_list_ = NULL; DevicePtr dev = create_device(CRAS_STREAM_OUTPUT, 1024, &format, CRAS_NODE_TYPE_HEADPHONE); DL_APPEND(dev_list_, dev->odev.get()); add_stream_to_dev(dev->dev, stream); dev_io_playback_fetch(dev_list_); // The next callback should be scheduled 10ms in the future. const timespec ten_millis = {0, 10 * 1000 * 1000}; add_timespecs(&start, &ten_millis); EXPECT_EQ(start.tv_sec, stream->rstream->next_cb_ts.tv_sec); EXPECT_EQ(start.tv_nsec, stream->rstream->next_cb_ts.tv_nsec); } // TODO(yuhsuan): There are some time scheduling rules in cras_iodev. Maybe we // can move them into dev_io so that all timing related codes are in the same // file or leave them in iodev_unittest like now. // 1. Device's wake_ts update: cras_iodev_frames_to_play_in_sleep. // 2. wake_ts update when removing stream: cras_iodev_rm_stream. /* Stubs */ extern "C" { 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; } float input_data_get_software_gain_scaler(struct input_data* data, float idev_sw_gain_scaler, struct cras_rstream* stream) { return 1.0; } struct cras_audio_format* cras_rstream_post_processing_format( const struct cras_rstream* stream, void* dev_ptr) { return NULL; } int cras_audio_thread_event_drop_samples() { return 0; } int cras_audio_thread_event_severe_underrun() { return 0; } void* buffer_share_get_data(const struct buffer_share* mix, unsigned int id) { return NULL; }; void cras_apm_list_start_apm(struct cras_apm_list* list, void* dev_ptr){}; void cras_apm_list_stop_apm(struct cras_apm_list* list, void* dev_ptr){}; } // extern "C" } // namespace int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); openlog(NULL, LOG_PERROR, LOG_USER); return RUN_ALL_TESTS(); }