cc343edb创建于 3月12日历史提交
/*
 * 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;
};

// ---------- Basic Open/Close ----------

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());
}

// ---------- Performance Mode ----------

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());
}

// ---------- Sample Format ----------

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());
}

// ---------- Sample Rate ----------

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());
}

// ---------- Channel Count ----------

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());
}

// ---------- Sharing Mode ----------

TEST_F(OHAudioStreamTest, OutputSharedMode)
{
    mBuilder.setDirection(Direction::Output)
            ->setFormat(AudioFormat::I16)
            ->setSharingMode(SharingMode::Shared);
    ASSERT_TRUE(openStream());
    ASSERT_TRUE(closeStream());
}

// ---------- Usage ----------

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());
}

// ---------- Input Preset ----------

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());
}

// ---------- Start / Stop ----------

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());
}

// ---------- Pause / Flush ----------

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());
}

// ---------- Callback Mode ----------

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());
}

// ---------- Buffer Size ----------

TEST_F(OHAudioStreamTest, OutputBufferSizePositive)
{
    mBuilder.setDirection(Direction::Output)
            ->setFormat(AudioFormat::I16);
    ASSERT_TRUE(openStream());
    ASSERT_GT(mStream->getBufferSizeInFrames(), 0);
    ASSERT_TRUE(closeStream());
}

// ---------- XRun Count ----------

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());
}

// ---------- Sync Write / Read ----------

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());
}

// ---------- Timestamp ----------

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());
}

// ---------- Double Open ----------

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());
}