* Copyright (C) 2026 Huawei Device Co., Ltd.
* 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>
using namespace oboe;
class OHAudioCallbackMonitor : public AudioStreamDataCallback {
public:
DataCallbackResult onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) override
{
callbackCount++;
framesPerCallback = numFrames;
return DataCallbackResult::Continue;
}
std::atomic<int32_t> callbackCount{0};
std::atomic<int32_t> framesPerCallback{0};
};
class OHAudioStreamTest : public ::testing::Test {
protected:
void TearDown() override
{
if (mStream) {
mStream->close();
mStream.reset();
}
}
bool openStream()
{
Result r = mBuilder.openStream(mStream);
EXPECT_EQ(r, Result::OK) << "Failed to open stream " << convertToText(r);
return (r == Result::OK);
}
bool closeStream()
{
if (mStream) {
Result r = mStream->close();
EXPECT_EQ(r, Result::OK) << "Failed to close stream " << convertToText(r);
mStream.reset();
return (r == Result::OK);
}
return true;
}
AudioStreamBuilder mBuilder;
std::shared_ptr<AudioStream> mStream;
};
TEST_F(OHAudioStreamTest, OpenCloseOutputStreamDefaultConfig)
{
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I16);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getAudioApi(), AudioApi::OHAudio);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OpenCloseInputStreamDefaultConfig)
{
mBuilder.setDirection(Direction::Input)
->setFormat(AudioFormat::I16);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getAudioApi(), AudioApi::OHAudio);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OpenCloseOutputLowLatency)
{
mBuilder.setDirection(Direction::Output)
->setPerformanceMode(PerformanceMode::LowLatency)
->setFormat(AudioFormat::I16);
ASSERT_TRUE(openStream());
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OpenCloseOutputNonePerformance)
{
mBuilder.setDirection(Direction::Output)
->setPerformanceMode(PerformanceMode::None)
->setFormat(AudioFormat::I16);
ASSERT_TRUE(openStream());
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OpenCloseInputLowLatency)
{
mBuilder.setDirection(Direction::Input)
->setPerformanceMode(PerformanceMode::LowLatency)
->setFormat(AudioFormat::I16);
ASSERT_TRUE(openStream());
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OutputFormatI16)
{
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I16);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getFormat(), AudioFormat::I16);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OutputFormatI24)
{
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I24);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getFormat(), AudioFormat::I24);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OutputFormatI32)
{
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I32);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getFormat(), AudioFormat::I32);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, InputFormatI16)
{
mBuilder.setDirection(Direction::Input)
->setFormat(AudioFormat::I16);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getFormat(), AudioFormat::I16);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OutputSampleRate44100)
{
constexpr int32_t sampleRate = 44100;
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I16)
->setSampleRate(sampleRate);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getSampleRate(), sampleRate);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OutputSampleRate48000)
{
constexpr int32_t sampleRate = 48000;
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I16)
->setSampleRate(sampleRate);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getSampleRate(), sampleRate);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OutputMono)
{
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I16)
->setChannelCount(1);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getChannelCount(), 1);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OutputStereo)
{
constexpr int32_t channelCount = 2;
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I16)
->setChannelCount(channelCount);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getChannelCount(), channelCount);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OutputSharedMode)
{
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I16)
->setSharingMode(SharingMode::Shared);
ASSERT_TRUE(openStream());
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OutputUsageMedia)
{
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I16)
->setUsage(Usage::Media);
ASSERT_TRUE(openStream());
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OutputUsageGame)
{
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I16)
->setUsage(Usage::Game);
ASSERT_TRUE(openStream());
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OutputUsageVoiceCommunication)
{
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I16)
->setUsage(Usage::VoiceCommunication);
ASSERT_TRUE(openStream());
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, InputPresetGeneric)
{
mBuilder.setDirection(Direction::Input)
->setFormat(AudioFormat::I16)
->setInputPreset(InputPreset::Generic);
ASSERT_TRUE(openStream());
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, InputPresetVoiceRecognition)
{
mBuilder.setDirection(Direction::Input)
->setFormat(AudioFormat::I16)
->setInputPreset(InputPreset::VoiceRecognition);
ASSERT_TRUE(openStream());
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, InputPresetVoiceCommunication)
{
mBuilder.setDirection(Direction::Input)
->setFormat(AudioFormat::I16)
->setInputPreset(InputPreset::VoiceCommunication);
ASSERT_TRUE(openStream());
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OutputStartStop)
{
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I16);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getState(), StreamState::Open);
Result r = mStream->requestStart();
ASSERT_EQ(r, Result::OK) << "Failed to start stream " << convertToText(r);
ASSERT_EQ(mStream->getState(), StreamState::Started);
r = mStream->requestStop();
ASSERT_EQ(r, Result::OK) << "Failed to stop stream " << convertToText(r);
ASSERT_EQ(mStream->getState(), StreamState::Stopped);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, InputStartStop)
{
mBuilder.setDirection(Direction::Input)
->setFormat(AudioFormat::I16);
ASSERT_TRUE(openStream());
Result r = mStream->requestStart();
ASSERT_EQ(r, Result::OK) << "Failed to start stream " << convertToText(r);
r = mStream->requestStop();
ASSERT_EQ(r, Result::OK) << "Failed to stop stream " << convertToText(r);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OutputPause)
{
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I16);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->requestStart(), Result::OK);
ASSERT_EQ(mStream->requestPause(), Result::OK);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OutputFlush)
{
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I16);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->requestStart(), Result::OK);
ASSERT_EQ(mStream->requestPause(), Result::OK);
ASSERT_EQ(mStream->requestFlush(), Result::OK);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OutputCallbackModeReceivesData)
{
OHAudioCallbackMonitor callback;
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I16)
->setPerformanceMode(PerformanceMode::LowLatency)
->setDataCallback(&callback);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->requestStart(), Result::OK);
int timeout = 40;
constexpr int32_t sleepTime = 50 * 1000;
while (callback.callbackCount == 0 && timeout > 0)
{
usleep(sleepTime);
timeout--;
}
ASSERT_GT(callback.callbackCount.load(), 0) << "Callback was never invoked";
ASSERT_GT(callback.framesPerCallback.load(), 0);
ASSERT_EQ(mStream->requestStop(), Result::OK);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, InputCallbackModeReceivesData)
{
OHAudioCallbackMonitor callback;
mBuilder.setDirection(Direction::Input)
->setFormat(AudioFormat::I16)
->setPerformanceMode(PerformanceMode::LowLatency)
->setDataCallback(&callback);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->requestStart(), Result::OK);
int timeout = 40;
constexpr int32_t sleepTime = 50 * 1000;
while (callback.callbackCount == 0 && timeout > 0)
{
usleep(sleepTime);
timeout--;
}
ASSERT_GT(callback.callbackCount.load(), 0) << "Callback was never invoked";
ASSERT_GT(callback.framesPerCallback.load(), 0);
ASSERT_EQ(mStream->requestStop(), Result::OK);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OutputBufferSizePositive)
{
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I16);
ASSERT_TRUE(openStream());
ASSERT_GT(mStream->getBufferSizeInFrames(), 0);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OutputXRunCountSupported)
{
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I16);
ASSERT_TRUE(openStream());
ASSERT_TRUE(mStream->isXRunCountSupported());
auto xRunResult = mStream->getXRunCount();
ASSERT_EQ(xRunResult.error(), Result::OK);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OutputSyncWrite)
{
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I16)
->setChannelCount(1);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->requestStart(), Result::OK);
const int kNumFrames = 256;
int16_t buffer[kNumFrames] = {};
auto result = mStream->write(buffer, kNumFrames, 0);
ASSERT_GE(result.value(), 0);
ASSERT_EQ(mStream->requestStop(), Result::OK);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, InputSyncRead)
{
mBuilder.setDirection(Direction::Input)
->setFormat(AudioFormat::I16)
->setChannelCount(1);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->requestStart(), Result::OK);
constexpr int32_t sleepTime = 100 * 1000;
usleep(sleepTime);
const int kNumFrames = 256;
int16_t buffer[kNumFrames] = {};
auto result = mStream->read(buffer, kNumFrames, 0);
ASSERT_GE(result.value(), 0);
ASSERT_EQ(mStream->requestStop(), Result::OK);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, OutputTimestampAfterStart)
{
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I16);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->requestStart(), Result::OK);
constexpr int32_t sleepTime = 200 * 1000;
usleep(sleepTime);
int64_t framePosition = 0;
int64_t timeNanoseconds = 0;
Result r = mStream->getTimestamp(CLOCK_MONOTONIC, &framePosition, &timeNanoseconds);
ASSERT_EQ(r, Result::OK);
ASSERT_EQ(mStream->requestStop(), Result::OK);
ASSERT_TRUE(closeStream());
}
TEST_F(OHAudioStreamTest, DoubleOpenReturnsFails)
{
mBuilder.setDirection(Direction::Output)
->setFormat(AudioFormat::I16);
ASSERT_TRUE(openStream());
Result r = mStream->open();
ASSERT_NE(r, Result::OK) << "Second open should fail";
ASSERT_TRUE(closeStream());
}