| #include <oboe/Oboe.h>/* |
| * Copyright 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <gtest/gtest.h> |
| #include <oboe/Oboe.h> |
| #include <android/api-level.h> |
| |
| using namespace oboe; |
| |
| class CallbackSizeMonitor : public AudioStreamCallback { |
| public: |
| DataCallbackResult onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) override { |
| framesPerCallback = numFrames; |
| callbackCount++; |
| return DataCallbackResult::Continue; |
| } |
| |
| // This is exposed publicly so that the number of frames per callback can be tested. |
| std::atomic<int32_t> framesPerCallback{0}; |
| std::atomic<int32_t> callbackCount{0}; |
| }; |
| |
| class StreamOpen : public ::testing::Test { |
| |
| protected: |
| |
| bool openStream() { |
| Result r = mBuilder.openStream(&mStream); |
| EXPECT_EQ(r, Result::OK) << "Failed to open stream " << convertToText(r); |
| EXPECT_EQ(0, openCount) << "Should start with a fresh object every time."; |
| openCount++; |
| return (r == Result::OK); |
| } |
| |
| bool closeStream() { |
| Result r = mStream->close(); |
| EXPECT_EQ(r, Result::OK) << "Failed to close stream. " << convertToText(r); |
| usleep(500 * 1000); // give previous stream time to settle |
| return (r == Result::OK); |
| } |
| |
| void checkSampleRateConversionAdvancing(Direction direction) { |
| CallbackSizeMonitor callback; |
| |
| mBuilder.setDirection(direction); |
| mBuilder.setAudioApi(AudioApi::AAudio); |
| mBuilder.setCallback(&callback); |
| mBuilder.setPerformanceMode(PerformanceMode::LowLatency); |
| mBuilder.setSampleRate(44100); |
| mBuilder.setSampleRateConversionQuality(SampleRateConversionQuality::Medium); |
| |
| ASSERT_TRUE(openStream()); |
| |
| ASSERT_EQ(mStream->requestStart(), Result::OK); |
| int timeout = 20; |
| while (callback.framesPerCallback == 0 && timeout > 0) { |
| usleep(50 * 1000); |
| timeout--; |
| } |
| |
| // Catch Issue #1166 |
| mStream->getTimestamp(CLOCK_MONOTONIC); // should not crash |
| mStream->getTimestamp(CLOCK_MONOTONIC, nullptr, nullptr); // should not crash |
| |
| ASSERT_GT(callback.callbackCount, 0); |
| ASSERT_GT(callback.framesPerCallback, 0); |
| ASSERT_EQ(mStream->requestStop(), Result::OK); |
| |
| ASSERT_TRUE(closeStream()); |
| } |
| |
| AudioStreamBuilder mBuilder; |
| AudioStream *mStream = nullptr; |
| int32_t openCount = 0; |
| |
| }; |
| |
| TEST_F(StreamOpen, ForOpenSLESDefaultSampleRateIsUsed){ |
| |
| DefaultStreamValues::SampleRate = 44100; |
| DefaultStreamValues::FramesPerBurst = 192; |
| mBuilder.setAudioApi(AudioApi::OpenSLES); |
| ASSERT_TRUE(openStream()); |
| ASSERT_EQ(mStream->getSampleRate(), 44100); |
| ASSERT_TRUE(closeStream()); |
| } |
| |
| TEST_F(StreamOpen, ForOpenSLESDefaultFramesPerBurstIsUsed){ |
| |
| DefaultStreamValues::SampleRate = 48000; |
| DefaultStreamValues::FramesPerBurst = 128; // used for low latency |
| mBuilder.setAudioApi(AudioApi::OpenSLES); |
| mBuilder.setPerformanceMode(PerformanceMode::LowLatency); |
| ASSERT_TRUE(openStream()); |
| ASSERT_EQ(mStream->getFramesPerBurst(), 128); |
| ASSERT_TRUE(closeStream()); |
| } |
| |
| TEST_F(StreamOpen, ForOpenSLESDefaultChannelCountIsUsed){ |
| |
| DefaultStreamValues::ChannelCount = 1; |
| mBuilder.setAudioApi(AudioApi::OpenSLES); |
| ASSERT_TRUE(openStream()); |
| ASSERT_EQ(mStream->getChannelCount(), 1); |
| ASSERT_TRUE(closeStream()); |
| } |
| |
| TEST_F(StreamOpen, OutputForOpenSLESPerformanceModeShouldBeNone){ |
| // We will not get a LowLatency stream if we request 16000 Hz. |
| mBuilder.setSampleRate(16000); |
| mBuilder.setPerformanceMode(PerformanceMode::LowLatency); |
| mBuilder.setDirection(Direction::Output); |
| mBuilder.setAudioApi(AudioApi::OpenSLES); |
| ASSERT_TRUE(openStream()); |
| ASSERT_EQ((int)mStream->getPerformanceMode(), (int)PerformanceMode::None); |
| ASSERT_TRUE(closeStream()); |
| } |
| |
| TEST_F(StreamOpen, InputForOpenSLESPerformanceModeShouldBeNone){ |
| // We will not get a LowLatency stream if we request 16000 Hz. |
| mBuilder.setSampleRate(16000); |
| mBuilder.setPerformanceMode(PerformanceMode::LowLatency); |
| mBuilder.setDirection(Direction::Input); |
| mBuilder.setAudioApi(AudioApi::OpenSLES); |
| ASSERT_TRUE(openStream()); |
| ASSERT_EQ((int)mStream->getPerformanceMode(), (int)PerformanceMode::None); |
| ASSERT_TRUE(closeStream()); |
| } |
| |
| TEST_F(StreamOpen, ForOpenSlesIllegalFormatRejectedOutput) { |
| mBuilder.setAudioApi(AudioApi::OpenSLES); |
| mBuilder.setPerformanceMode(PerformanceMode::LowLatency); |
| mBuilder.setFormat(static_cast<AudioFormat>(666)); |
| Result r = mBuilder.openStream(&mStream); |
| EXPECT_NE(r, Result::OK) << "Should not open stream " << convertToText(r); |
| if (mStream != nullptr) { |
| mStream->close(); // just in case it accidentally opened |
| } |
| } |
| |
| TEST_F(StreamOpen, ForOpenSlesIllegalFormatRejectedInput) { |
| mBuilder.setAudioApi(AudioApi::OpenSLES); |
| mBuilder.setPerformanceMode(PerformanceMode::LowLatency); |
| mBuilder.setDirection(Direction::Input); |
| mBuilder.setFormat(static_cast<AudioFormat>(666)); |
| Result r = mBuilder.openStream(&mStream); |
| EXPECT_NE(r, Result::OK) << "Should not open stream " << convertToText(r); |
| if (mStream != nullptr) { |
| mStream->close(); // just in case it accidentally opened |
| } |
| } |
| |
| // Make sure the callback is called with the requested FramesPerCallback |
| TEST_F(StreamOpen, OpenSLESFramesPerCallback) { |
| const int kRequestedFramesPerCallback = 417; |
| CallbackSizeMonitor callback; |
| |
| DefaultStreamValues::SampleRate = 48000; |
| DefaultStreamValues::ChannelCount = 2; |
| DefaultStreamValues::FramesPerBurst = 192; |
| mBuilder.setAudioApi(AudioApi::OpenSLES); |
| mBuilder.setFramesPerCallback(kRequestedFramesPerCallback); |
| mBuilder.setCallback(&callback); |
| ASSERT_TRUE(openStream()); |
| ASSERT_EQ(mStream->requestStart(), Result::OK); |
| int timeout = 20; |
| while (callback.framesPerCallback == 0 && timeout > 0) { |
| usleep(50 * 1000); |
| timeout--; |
| } |
| ASSERT_EQ(kRequestedFramesPerCallback, callback.framesPerCallback); |
| ASSERT_EQ(kRequestedFramesPerCallback, mStream->getFramesPerCallback()); |
| ASSERT_EQ(mStream->requestStop(), Result::OK); |
| ASSERT_TRUE(closeStream()); |
| } |
| |
| /* TODO - This is hanging! |
| // Make sure the LowLatency callback has the requested FramesPerCallback. |
| TEST_F(StreamOpen, AAudioFramesPerCallbackLowLatency) { |
| const int kRequestedFramesPerCallback = 192; |
| CallbackSizeMonitor callback; |
| |
| mBuilder.setAudioApi(AudioApi::AAudio); |
| mBuilder.setFramesPerCallback(kRequestedFramesPerCallback); |
| mBuilder.setCallback(&callback); |
| mBuilder.setPerformanceMode(PerformanceMode::LowLatency); |
| ASSERT_TRUE(openStream()); |
| ASSERT_EQ(kRequestedFramesPerCallback, mStream->getFramesPerCallback()); |
| ASSERT_EQ(mStream->requestStart(), Result::OK); |
| int timeout = 20; |
| while (callback.framesPerCallback == 0 && timeout > 0) { |
| usleep(50 * 1000); |
| timeout--; |
| } |
| ASSERT_EQ(kRequestedFramesPerCallback, callback.framesPerCallback); |
| ASSERT_EQ(mStream->requestStop(), Result::OK); |
| ASSERT_TRUE(closeStream()); |
| } |
| */ |
| |
| /* TODO - This is hanging! |
| // Make sure the regular callback has the requested FramesPerCallback. |
| TEST_F(StreamOpen, AAudioFramesPerCallbackNone) { |
| const int kRequestedFramesPerCallback = 1024; |
| CallbackSizeMonitor callback; |
| |
| mBuilder.setAudioApi(AudioApi::AAudio); |
| mBuilder.setFramesPerCallback(kRequestedFramesPerCallback); |
| mBuilder.setCallback(&callback); |
| mBuilder.setPerformanceMode(PerformanceMode::None); |
| ASSERT_TRUE(openStream()); |
| ASSERT_EQ(kRequestedFramesPerCallback, mStream->getFramesPerCallback()); |
| ASSERT_EQ(mStream->setBufferSizeInFrames(mStream->getBufferCapacityInFrames()), Result::OK); |
| ASSERT_EQ(mStream->requestStart(), Result::OK); |
| int timeout = 20; |
| while (callback.framesPerCallback == 0 && timeout > 0) { |
| usleep(50 * 1000); |
| timeout--; |
| } |
| ASSERT_EQ(kRequestedFramesPerCallback, callback.framesPerCallback); |
| ASSERT_EQ(mStream->requestStop(), Result::OK); |
| ASSERT_TRUE(closeStream()); |
| } |
| */ |
| |
| TEST_F(StreamOpen, RecordingFormatUnspecifiedReturnsI16BeforeMarshmallow){ |
| |
| if (getSdkVersion() < __ANDROID_API_M__){ |
| mBuilder.setDirection(Direction::Input); |
| mBuilder.setFormat(AudioFormat::Unspecified); |
| ASSERT_TRUE(openStream()); |
| ASSERT_EQ(mStream->getFormat(), AudioFormat::I16); |
| ASSERT_TRUE(closeStream()); |
| } |
| } |
| |
| TEST_F(StreamOpen, RecordingFormatUnspecifiedReturnsFloatOnMarshmallowAndLater){ |
| |
| if (getSdkVersion() >= __ANDROID_API_M__){ |
| mBuilder.setDirection(Direction::Input); |
| mBuilder.setFormat(AudioFormat::Unspecified); |
| ASSERT_TRUE(openStream()); |
| ASSERT_EQ(mStream->getFormat(), AudioFormat::Float); |
| ASSERT_TRUE(closeStream()); |
| } |
| } |
| |
| TEST_F(StreamOpen, RecordingFormatFloatReturnsErrorBeforeMarshmallow){ |
| |
| if (getSdkVersion() < __ANDROID_API_M__){ |
| mBuilder.setDirection(Direction::Input); |
| mBuilder.setFormat(AudioFormat::Float); |
| Result r = mBuilder.openStream(&mStream); |
| ASSERT_EQ(r, Result::ErrorInvalidFormat) << convertToText(r); |
| ASSERT_TRUE(closeStream()); |
| } |
| } |
| |
| TEST_F(StreamOpen, RecordingFormatFloatReturnsFloatOnMarshmallowAndLater){ |
| |
| if (getSdkVersion() >= __ANDROID_API_M__){ |
| mBuilder.setDirection(Direction::Input); |
| mBuilder.setFormat(AudioFormat::Float); |
| ASSERT_TRUE(openStream()); |
| ASSERT_EQ(mStream->getFormat(), AudioFormat::Float); |
| ASSERT_TRUE(closeStream()); |
| } |
| } |
| |
| TEST_F(StreamOpen, RecordingFormatI16ReturnsI16){ |
| |
| mBuilder.setDirection(Direction::Input); |
| mBuilder.setFormat(AudioFormat::I16); |
| ASSERT_TRUE(openStream()); |
| ASSERT_EQ(mStream->getFormat(), AudioFormat::I16); |
| ASSERT_TRUE(closeStream()); |
| } |
| |
| TEST_F(StreamOpen, PlaybackFormatUnspecifiedReturnsI16BeforeLollipop){ |
| |
| if (getSdkVersion() < __ANDROID_API_L__){ |
| mBuilder.setDirection(Direction::Output); |
| mBuilder.setFormat(AudioFormat::Unspecified); |
| ASSERT_TRUE(openStream()); |
| ASSERT_EQ(mStream->getFormat(), AudioFormat::I16); |
| ASSERT_TRUE(closeStream()); |
| } |
| } |
| |
| TEST_F(StreamOpen, PlaybackFormatUnspecifiedReturnsFloatOnLollipopAndLater){ |
| |
| if (getSdkVersion() >= __ANDROID_API_L__){ |
| mBuilder.setDirection(Direction::Output); |
| mBuilder.setFormat(AudioFormat::Unspecified); |
| ASSERT_TRUE(openStream()); |
| ASSERT_EQ(mStream->getFormat(), AudioFormat::Float); |
| ASSERT_TRUE(closeStream()); |
| } |
| } |
| |
| TEST_F(StreamOpen, PlaybackFormatFloatReturnsErrorBeforeLollipop){ |
| |
| if (getSdkVersion() < __ANDROID_API_L__){ |
| mBuilder.setDirection(Direction::Output); |
| mBuilder.setFormat(AudioFormat::Float); |
| Result r = mBuilder.openStream(&mStream); |
| ASSERT_EQ(r, Result::ErrorInvalidFormat); |
| ASSERT_TRUE(closeStream()); |
| } |
| } |
| |
| TEST_F(StreamOpen, PlaybackFormatFloatReturnsFloatOnLollipopAndLater){ |
| |
| if (getSdkVersion() >= __ANDROID_API_L__){ |
| mBuilder.setDirection(Direction::Output); |
| mBuilder.setFormat(AudioFormat::Float); |
| ASSERT_TRUE(openStream()); |
| ASSERT_EQ(mStream->getFormat(), AudioFormat::Float); |
| ASSERT_TRUE(closeStream()); |
| } |
| } |
| |
| TEST_F(StreamOpen, PlaybackFormatI16ReturnsI16) { |
| mBuilder.setDirection(Direction::Output); |
| mBuilder.setFormat(AudioFormat::I16); |
| ASSERT_TRUE(openStream()); |
| ASSERT_EQ(mStream->getFormat(), AudioFormat::I16); |
| ASSERT_TRUE(closeStream()); |
| } |
| |
| TEST_F(StreamOpen, OpenCloseLowLatencyStream){ |
| mBuilder.setDirection(Direction::Output); |
| mBuilder.setPerformanceMode(PerformanceMode::LowLatency); |
| float *buf = new float[100]; |
| ASSERT_TRUE(openStream()); |
| delete[] buf; |
| ASSERT_TRUE(closeStream()); |
| } |
| |
| TEST_F(StreamOpen, LowLatencyStreamHasSmallBufferSize){ |
| |
| if (mBuilder.isAAudioRecommended()) { |
| mBuilder.setDirection(Direction::Output); |
| mBuilder.setPerformanceMode(PerformanceMode::LowLatency); |
| ASSERT_TRUE(openStream()); |
| int32_t bufferSize = mStream->getBufferSizeInFrames(); |
| int32_t burst = mStream->getFramesPerBurst(); |
| ASSERT_TRUE(closeStream()); |
| ASSERT_LE(bufferSize, burst * 3); |
| } |
| } |
| |
| // See if sample rate conversion by Oboe is calling the callback. |
| TEST_F(StreamOpen, AAudioOutputSampleRate44100) { |
| checkSampleRateConversionAdvancing(Direction::Output); |
| } |
| |
| // See if sample rate conversion by Oboe is calling the callback. |
| TEST_F(StreamOpen, AAudioInputSampleRate44100) { |
| checkSampleRateConversionAdvancing(Direction::Input); |
| } |