578 lines
25 KiB
C++
578 lines
25 KiB
C++
/*
|
|
* SensoryKeywordDetectorTest.cpp
|
|
*
|
|
* Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
|
* You may not use this file except in compliance with the License.
|
|
* A copy of the License is located at
|
|
*
|
|
* http://aws.amazon.com/apache2.0/
|
|
*
|
|
* or in the "license" file accompanying this file. This file 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.
|
|
*/
|
|
|
|
/// @file SensoryKeyWordDetectorTest.cpp
|
|
|
|
#include <chrono>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <AVSCommon/SDKInterfaces/KeyWordObserverInterface.h>
|
|
#include <AVSCommon/SDKInterfaces/KeyWordDetectorStateObserverInterface.h>
|
|
#include <AVSCommon/AVS/AudioInputStream.h>
|
|
#include <AVSCommon/Utils/SDS/SharedDataStream.h>
|
|
|
|
#include "Sensory/SensoryKeywordDetector.h"
|
|
|
|
namespace alexaClientSDK {
|
|
namespace kwd {
|
|
namespace test {
|
|
|
|
using namespace avsCommon;
|
|
using namespace avsCommon::sdkInterfaces;
|
|
using namespace avsCommon::utils;
|
|
|
|
/// The path to the inputs folder that should be passed in via command line argument.
|
|
std::string inputsDirPath;
|
|
|
|
/// The name of the Alexa model file for Sensory.
|
|
static const std::string MODEL_FILE = "/SensoryModels/spot-alexa-rpi-31000.snsr";
|
|
|
|
/// The keyword that Sensory emits for the above model file
|
|
static const std::string KEYWORD = "alexa";
|
|
|
|
/// The name of a test audio file.
|
|
static const std::string FOUR_ALEXAS_AUDIO_FILE = "/four_alexa.wav";
|
|
|
|
/// The name of a test audio file.
|
|
static const std::string ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE = "/alexa_stop_alexa_joke.wav";
|
|
|
|
/// The number of samples per millisecond, assuming a sample rate of 16 kHz.
|
|
static const int SAMPLES_PER_MS = 16;
|
|
|
|
/// The margin in milliseconds for testing indices of keyword detections.
|
|
static const std::chrono::milliseconds MARGIN = std::chrono::milliseconds(250);
|
|
|
|
/// The margin in samples for testing indices of keyword detections.
|
|
static const AudioInputStream::Index MARGIN_IN_SAMPLES = MARGIN.count() * SAMPLES_PER_MS;
|
|
|
|
/// The number of "Alexa" keywords in the four_alexa.wav file.
|
|
static const size_t NUM_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE = 4;
|
|
|
|
/// The approximate begin indices of the four "Alexa" keywords in the four_alexa.wav file.
|
|
std::vector<AudioInputStream::Index> BEGIN_INDICES_OF_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE = {7520, 39680, 58880, 77120};
|
|
|
|
/// The approximate end indices of the four "Alexa" hotwords in the four_alexa.wav file.
|
|
std::vector<AudioInputStream::Index> END_INDICES_OF_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE = {21440, 52800, 72480, 91552};
|
|
|
|
/// The number of "Alexa" keywords in the alexa_stop_alexa_joke.wav file.
|
|
static const size_t NUM_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE = 2;
|
|
|
|
/// The approximate begin indices of the two "Alexa" keywords in the alexa_stop_alexa_joke.wav file.
|
|
std::vector<AudioInputStream::Index> BEGIN_INDICES_OF_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE = {8000, 38240};
|
|
|
|
/// The approximate end indices of the two "Alexa" keywords in the alexa_stop_alexa_joke.wav file.
|
|
std::vector<AudioInputStream::Index> END_INDICES_OF_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE = {20960, 51312};
|
|
|
|
/// The compatible encoding for Sensory.
|
|
static const avsCommon::utils::AudioFormat::Encoding COMPATIBLE_ENCODING =
|
|
avsCommon::utils::AudioFormat::Encoding::LPCM;
|
|
|
|
/// The compatible endianness for Sensory.
|
|
static const avsCommon::utils::AudioFormat::Endianness COMPATIBLE_ENDIANNESS =
|
|
avsCommon::utils::AudioFormat::Endianness::LITTLE;
|
|
|
|
/// The compatible sample rate for Sensory.
|
|
static const unsigned int COMPATIBLE_SAMPLE_RATE = 16000;
|
|
|
|
/// The compatible bits per sample for Sensory.
|
|
static const unsigned int COMPATIBLE_SAMPLE_SIZE_IN_BITS = 16;
|
|
|
|
/// The compatible number of channels for Sensory.
|
|
static const unsigned int COMPATIBLE_NUM_CHANNELS = 1;
|
|
|
|
/// Timeout for expected callbacks.
|
|
static const auto DEFAULT_TIMEOUT = std::chrono::milliseconds(4000);
|
|
|
|
/// A test observer that mocks out the KeyWordObserverInterface##onKeyWordDetected() call.
|
|
class testKeyWordObserver : public KeyWordObserverInterface {
|
|
public:
|
|
/// A struct used for bookkeeping of keyword detections.
|
|
struct detectionResult {
|
|
AudioInputStream::Index beginIndex;
|
|
AudioInputStream::Index endIndex;
|
|
std::string keyword;
|
|
};
|
|
|
|
/// Implementation of the KeyWordObserverInterface##onKeyWordDetected() call.
|
|
void onKeyWordDetected(
|
|
std::shared_ptr<AudioInputStream> stream,
|
|
std::string keyword,
|
|
AudioInputStream::Index beginIndex,
|
|
AudioInputStream::Index endIndex) {
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
m_detectionResults.push_back({beginIndex, endIndex, keyword});
|
|
m_detectionOccurred.notify_one();
|
|
};
|
|
|
|
/**
|
|
* Waits for the KeyWordObserverInterface##onKeyWordDetected() call N times.
|
|
*
|
|
* @param numDetectionsExpected The number of detections expected.
|
|
* @param timeout The amount of time to wait for the calls.
|
|
* @return The detection results that actually occurred.
|
|
*/
|
|
std::vector<detectionResult> waitForNDetections(
|
|
unsigned int numDetectionsExpected,
|
|
std::chrono::milliseconds timeout) {
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
m_detectionOccurred.wait_for(lock, timeout, [this, numDetectionsExpected]() {
|
|
return m_detectionResults.size() == numDetectionsExpected;
|
|
});
|
|
return m_detectionResults;
|
|
}
|
|
|
|
private:
|
|
/// The detection results that have occurred.
|
|
std::vector<detectionResult> m_detectionResults;
|
|
|
|
/// A lock to guard against new detections.
|
|
std::mutex m_mutex;
|
|
|
|
/// A condition variable to wait for detection calls.
|
|
std::condition_variable m_detectionOccurred;
|
|
};
|
|
|
|
/// A test observer that mocks out the KeyWordDetectorStateObserverInterface##onStateChanged() call.
|
|
class testStateObserver : public KeyWordDetectorStateObserverInterface {
|
|
public:
|
|
/**
|
|
* Constructor.
|
|
*/
|
|
testStateObserver() :
|
|
m_state(KeyWordDetectorStateObserverInterface::KeyWordDetectorState::STREAM_CLOSED),
|
|
m_stateChangeOccurred{false} {
|
|
}
|
|
|
|
/// Implementation of the KeyWordDetectorStateObserverInterface##onStateChanged() call.
|
|
void onStateChanged(KeyWordDetectorStateObserverInterface::KeyWordDetectorState keyWordDetectorState) {
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
m_state = keyWordDetectorState;
|
|
m_stateChangeOccurred = true;
|
|
m_stateChanged.notify_one();
|
|
}
|
|
|
|
/**
|
|
* Waits for the KeyWordDetectorStateObserverInterface##onStateChanged() call.
|
|
*
|
|
* @param timeout The amount of time to wait for the call.
|
|
* @param stateChanged An output parameter that notifies the caller whether a call occurred.
|
|
* @return Returns the state of the observer.
|
|
*/
|
|
KeyWordDetectorStateObserverInterface::KeyWordDetectorState waitForStateChange(
|
|
std::chrono::milliseconds timeout,
|
|
bool* stateChanged) {
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
bool success = m_stateChanged.wait_for(lock, timeout, [this]() { return m_stateChangeOccurred; });
|
|
|
|
if (!success) {
|
|
*stateChanged = false;
|
|
} else {
|
|
m_stateChangeOccurred = false;
|
|
*stateChanged = true;
|
|
}
|
|
return m_state;
|
|
}
|
|
|
|
private:
|
|
/// The state of the observer.
|
|
KeyWordDetectorStateObserverInterface::KeyWordDetectorState m_state;
|
|
|
|
/// A boolean flag so that we can re-use the observer even after a callback has occurred.
|
|
bool m_stateChangeOccurred;
|
|
|
|
/// A lock to guard against state changes.
|
|
std::mutex m_mutex;
|
|
|
|
/// A condition variable to wait for state changes.
|
|
std::condition_variable m_stateChanged;
|
|
};
|
|
|
|
class SensoryKeywordTest : public ::testing::Test {
|
|
protected:
|
|
/**
|
|
* Reads audio from a WAV file.
|
|
*
|
|
* @param fileName The path of the file to read from.
|
|
* @param [out] errorOccurred Lets users know if any errors occurred while parsing the file.
|
|
* @return A vector of int16_t containing the raw audio data of the WAV file without the RIFF header.
|
|
*/
|
|
std::vector<int16_t> readAudioFromFile(const std::string& fileName, bool* errorOccurred) {
|
|
const int RIFF_HEADER_SIZE = 44;
|
|
|
|
std::ifstream inputFile(fileName.c_str(), std::ifstream::binary);
|
|
if (!inputFile.good()) {
|
|
std::cout << "Couldn't open audio file!" << std::endl;
|
|
if (errorOccurred) {
|
|
*errorOccurred = true;
|
|
}
|
|
return {};
|
|
}
|
|
inputFile.seekg(0, std::ios::end);
|
|
int fileLengthInBytes = inputFile.tellg();
|
|
if (fileLengthInBytes <= RIFF_HEADER_SIZE) {
|
|
std::cout << "File should be larger than 44 bytes, which is the size of the RIFF header" << std::endl;
|
|
if (errorOccurred) {
|
|
*errorOccurred = true;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
inputFile.seekg(RIFF_HEADER_SIZE, std::ios::beg);
|
|
|
|
int numSamples = (fileLengthInBytes - RIFF_HEADER_SIZE) / 2;
|
|
|
|
std::vector<int16_t> retVal(numSamples, 0);
|
|
|
|
inputFile.read((char*)&retVal[0], numSamples * 2);
|
|
|
|
if (inputFile.gcount() != numSamples * 2) {
|
|
std::cout << "Error reading audio file" << std::endl;
|
|
if (errorOccurred) {
|
|
*errorOccurred = true;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
inputFile.close();
|
|
if (errorOccurred) {
|
|
*errorOccurred = false;
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
/**
|
|
* Checks to see that the expected keyword detection results are present.
|
|
*
|
|
* @param results A vector of @c detectionResult.
|
|
* @param expectedBeginIndex The expected begin index of the keyword.
|
|
* @param expectedEndIndex The expected end index of the keyword.
|
|
* @param expectedKeyword The expected keyword.
|
|
* @return @c true if the result is present within the margin and @c false otherwise.
|
|
*/
|
|
bool isResultPresent(
|
|
std::vector<testKeyWordObserver::detectionResult>& results,
|
|
AudioInputStream::Index expectedBeginIndex,
|
|
AudioInputStream::Index expectedEndIndex,
|
|
const std::string& expectedKeyword) {
|
|
AudioInputStream::Index highBoundOfBeginIndex = expectedBeginIndex + MARGIN_IN_SAMPLES;
|
|
AudioInputStream::Index lowBoundOfBeginIndex = expectedBeginIndex - MARGIN_IN_SAMPLES;
|
|
AudioInputStream::Index highBoundOfEndIndex = expectedEndIndex + MARGIN_IN_SAMPLES;
|
|
AudioInputStream::Index lowBoundOfEndIndex = expectedEndIndex - MARGIN_IN_SAMPLES;
|
|
for (auto result : results) {
|
|
if (result.endIndex <= highBoundOfEndIndex && result.endIndex >= lowBoundOfEndIndex &&
|
|
result.beginIndex <= highBoundOfBeginIndex && result.beginIndex >= lowBoundOfBeginIndex &&
|
|
expectedKeyword == result.keyword) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<testKeyWordObserver> keyWordObserver1;
|
|
|
|
std::shared_ptr<testKeyWordObserver> keyWordObserver2;
|
|
|
|
std::shared_ptr<testStateObserver> stateObserver;
|
|
|
|
AudioFormat compatibleAudioFormat;
|
|
|
|
std::string modelFilePath;
|
|
|
|
virtual void SetUp() {
|
|
keyWordObserver1 = std::make_shared<testKeyWordObserver>();
|
|
keyWordObserver2 = std::make_shared<testKeyWordObserver>();
|
|
stateObserver = std::make_shared<testStateObserver>();
|
|
|
|
compatibleAudioFormat.sampleRateHz = COMPATIBLE_SAMPLE_RATE;
|
|
compatibleAudioFormat.sampleSizeInBits = COMPATIBLE_SAMPLE_SIZE_IN_BITS;
|
|
compatibleAudioFormat.numChannels = COMPATIBLE_NUM_CHANNELS;
|
|
compatibleAudioFormat.endianness = COMPATIBLE_ENDIANNESS;
|
|
compatibleAudioFormat.encoding = COMPATIBLE_ENCODING;
|
|
|
|
std::ifstream filePresent((inputsDirPath + MODEL_FILE).c_str());
|
|
ASSERT_TRUE(filePresent.good()) << "Unable to find " + inputsDirPath + MODEL_FILE
|
|
<< ". Please place model file within this location.";
|
|
|
|
modelFilePath = inputsDirPath + MODEL_FILE;
|
|
}
|
|
};
|
|
|
|
/// Tests that we don't get back a valid detector if an invalid stream is passed in.
|
|
TEST_F(SensoryKeywordTest, invalidStream) {
|
|
auto detector = SensoryKeywordDetector::create(
|
|
nullptr, compatibleAudioFormat, {keyWordObserver1}, {stateObserver}, modelFilePath);
|
|
ASSERT_FALSE(detector);
|
|
}
|
|
|
|
/// Tests that we don't get back a valid detector if an invalid endianness is passed in.
|
|
TEST_F(SensoryKeywordTest, incompatibleEndianness) {
|
|
auto rawBuffer = std::make_shared<avsCommon::avs::AudioInputStream::Buffer>(500000);
|
|
auto uniqueSds = avsCommon::avs::AudioInputStream::create(rawBuffer, 2, 1);
|
|
std::shared_ptr<AudioInputStream> sds = std::move(uniqueSds);
|
|
|
|
compatibleAudioFormat.endianness = AudioFormat::Endianness::BIG;
|
|
|
|
auto detector =
|
|
SensoryKeywordDetector::create(sds, compatibleAudioFormat, {keyWordObserver1}, {stateObserver}, modelFilePath);
|
|
ASSERT_FALSE(detector);
|
|
}
|
|
|
|
/// Tests that we get back the expected number of keywords for the four_alexa.wav file for one keyword observer.
|
|
TEST_F(SensoryKeywordTest, getExpectedNumberOfDetectionsInFourAlexasAudioFileForOneObserver) {
|
|
auto fourAlexasBuffer = std::make_shared<avsCommon::avs::AudioInputStream::Buffer>(500000);
|
|
auto fourAlexasSds = avsCommon::avs::AudioInputStream::create(fourAlexasBuffer, 2, 1);
|
|
std::shared_ptr<AudioInputStream> fourAlexasAudioBuffer = std::move(fourAlexasSds);
|
|
|
|
std::unique_ptr<AudioInputStream::Writer> fourAlexasAudioBufferWriter =
|
|
fourAlexasAudioBuffer->createWriter(avsCommon::avs::AudioInputStream::Writer::Policy::NONBLOCKABLE);
|
|
|
|
std::string audioFilePath = inputsDirPath + FOUR_ALEXAS_AUDIO_FILE;
|
|
bool error;
|
|
std::vector<int16_t> audioData = readAudioFromFile(audioFilePath, &error);
|
|
ASSERT_FALSE(error);
|
|
|
|
fourAlexasAudioBufferWriter->write(audioData.data(), audioData.size());
|
|
|
|
auto detector = SensoryKeywordDetector::create(
|
|
fourAlexasAudioBuffer, compatibleAudioFormat, {keyWordObserver1}, {stateObserver}, modelFilePath);
|
|
ASSERT_TRUE(detector);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
|
auto detections =
|
|
keyWordObserver1->waitForNDetections(END_INDICES_OF_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE.size(), DEFAULT_TIMEOUT);
|
|
ASSERT_EQ(detections.size(), NUM_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE);
|
|
|
|
for (unsigned int i = 0; i < END_INDICES_OF_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE.size(); ++i) {
|
|
ASSERT_TRUE(isResultPresent(
|
|
detections,
|
|
BEGIN_INDICES_OF_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE.at(i),
|
|
END_INDICES_OF_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE.at(i),
|
|
KEYWORD));
|
|
}
|
|
}
|
|
|
|
/// Tests that we get back the expected number of keywords for the four_alexa.wav file for two keyword observers.
|
|
TEST_F(SensoryKeywordTest, getExpectedNumberOfDetectionsInFourAlexasAudioFileForTwoObservers) {
|
|
auto fourAlexasBuffer = std::make_shared<avsCommon::avs::AudioInputStream::Buffer>(500000);
|
|
auto fourAlexasSds = avsCommon::avs::AudioInputStream::create(fourAlexasBuffer, 2, 1);
|
|
std::shared_ptr<AudioInputStream> fourAlexasAudioBuffer = std::move(fourAlexasSds);
|
|
|
|
std::unique_ptr<AudioInputStream::Writer> fourAlexasAudioBufferWriter =
|
|
fourAlexasAudioBuffer->createWriter(avsCommon::avs::AudioInputStream::Writer::Policy::NONBLOCKABLE);
|
|
|
|
std::string audioFilePath = inputsDirPath + FOUR_ALEXAS_AUDIO_FILE;
|
|
bool error;
|
|
std::vector<int16_t> audioData = readAudioFromFile(audioFilePath, &error);
|
|
ASSERT_FALSE(error);
|
|
|
|
fourAlexasAudioBufferWriter->write(audioData.data(), audioData.size());
|
|
|
|
auto detector = SensoryKeywordDetector::create(
|
|
fourAlexasAudioBuffer,
|
|
compatibleAudioFormat,
|
|
{keyWordObserver1, keyWordObserver2},
|
|
{stateObserver},
|
|
modelFilePath);
|
|
ASSERT_TRUE(detector);
|
|
auto detections = keyWordObserver1->waitForNDetections(NUM_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE, DEFAULT_TIMEOUT);
|
|
ASSERT_EQ(detections.size(), NUM_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE);
|
|
|
|
for (unsigned int i = 0; i < END_INDICES_OF_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE.size(); ++i) {
|
|
ASSERT_TRUE(isResultPresent(
|
|
detections,
|
|
BEGIN_INDICES_OF_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE.at(i),
|
|
END_INDICES_OF_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE.at(i),
|
|
KEYWORD));
|
|
}
|
|
|
|
detections = keyWordObserver2->waitForNDetections(NUM_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE, DEFAULT_TIMEOUT);
|
|
ASSERT_EQ(detections.size(), NUM_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE);
|
|
|
|
for (unsigned int i = 0; i < END_INDICES_OF_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE.size(); ++i) {
|
|
ASSERT_TRUE(isResultPresent(
|
|
detections,
|
|
BEGIN_INDICES_OF_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE.at(i),
|
|
END_INDICES_OF_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE.at(i),
|
|
KEYWORD));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tests that we get back the expected number of keywords for the alexa_stop_alexa_joke.wav file for one keyword
|
|
* observer.
|
|
*/
|
|
TEST_F(SensoryKeywordTest, getExpectedNumberOfDetectionsInAlexaStopAlexaJokeAudioFileForOneObserver) {
|
|
auto alexaStopAlexaJokeBuffer = std::make_shared<avsCommon::avs::AudioInputStream::Buffer>(500000);
|
|
auto alexaStopAlexaJokeSds = avsCommon::avs::AudioInputStream::create(alexaStopAlexaJokeBuffer, 2, 1);
|
|
std::shared_ptr<AudioInputStream> alexaStopAlexaJokeAudioBuffer = std::move(alexaStopAlexaJokeSds);
|
|
|
|
std::unique_ptr<AudioInputStream::Writer> alexaStopAlexaJokeAudioBufferWriter =
|
|
alexaStopAlexaJokeAudioBuffer->createWriter(avsCommon::avs::AudioInputStream::Writer::Policy::NONBLOCKABLE);
|
|
|
|
std::string audioFilePath = inputsDirPath + ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE;
|
|
bool error;
|
|
std::vector<int16_t> audioData = readAudioFromFile(audioFilePath, &error);
|
|
ASSERT_FALSE(error);
|
|
|
|
alexaStopAlexaJokeAudioBufferWriter->write(audioData.data(), audioData.size());
|
|
|
|
auto detector = SensoryKeywordDetector::create(
|
|
alexaStopAlexaJokeAudioBuffer, compatibleAudioFormat, {keyWordObserver1}, {stateObserver}, modelFilePath);
|
|
ASSERT_TRUE(detector);
|
|
|
|
auto detections =
|
|
keyWordObserver1->waitForNDetections(NUM_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE, DEFAULT_TIMEOUT);
|
|
|
|
ASSERT_EQ(detections.size(), NUM_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE);
|
|
|
|
for (unsigned int i = 0; i < END_INDICES_OF_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE.size(); ++i) {
|
|
ASSERT_TRUE(isResultPresent(
|
|
detections,
|
|
BEGIN_INDICES_OF_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE.at(i),
|
|
END_INDICES_OF_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE.at(i),
|
|
KEYWORD));
|
|
}
|
|
}
|
|
|
|
/// Tests that the detector state changes to ACTIVE when the detector is initialized properly.
|
|
TEST_F(SensoryKeywordTest, getActiveState) {
|
|
auto alexaStopAlexaJokeBuffer = std::make_shared<avsCommon::avs::AudioInputStream::Buffer>(500000);
|
|
auto alexaStopAlexaJokeSds = avsCommon::avs::AudioInputStream::create(alexaStopAlexaJokeBuffer, 2, 1);
|
|
std::shared_ptr<AudioInputStream> alexaStopAlexaJokeAudioBuffer = std::move(alexaStopAlexaJokeSds);
|
|
|
|
std::unique_ptr<AudioInputStream::Writer> alexaStopAlexaJokeAudioBufferWriter =
|
|
alexaStopAlexaJokeAudioBuffer->createWriter(avsCommon::avs::AudioInputStream::Writer::Policy::NONBLOCKABLE);
|
|
|
|
std::string audioFilePath = inputsDirPath + ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE;
|
|
bool error;
|
|
std::vector<int16_t> audioData = readAudioFromFile(audioFilePath, &error);
|
|
ASSERT_FALSE(error);
|
|
|
|
alexaStopAlexaJokeAudioBufferWriter->write(audioData.data(), audioData.size());
|
|
|
|
auto detector = SensoryKeywordDetector::create(
|
|
alexaStopAlexaJokeAudioBuffer, compatibleAudioFormat, {keyWordObserver1}, {stateObserver}, modelFilePath);
|
|
ASSERT_TRUE(detector);
|
|
bool stateChanged = false;
|
|
KeyWordDetectorStateObserverInterface::KeyWordDetectorState stateReceived =
|
|
stateObserver->waitForStateChange(DEFAULT_TIMEOUT, &stateChanged);
|
|
ASSERT_TRUE(stateChanged);
|
|
ASSERT_EQ(stateReceived, KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE);
|
|
}
|
|
|
|
/**
|
|
* Tests that the stream is closed and that the detector state changes to STREAM_CLOSED when we close the only writer
|
|
* of the SDS passed in and all keyword detections have occurred.
|
|
*/
|
|
TEST_F(SensoryKeywordTest, getStreamClosedState) {
|
|
auto alexaStopAlexaJokeBuffer = std::make_shared<avsCommon::avs::AudioInputStream::Buffer>(500000);
|
|
auto alexaStopAlexaJokeSds = avsCommon::avs::AudioInputStream::create(alexaStopAlexaJokeBuffer, 2, 1);
|
|
std::shared_ptr<AudioInputStream> alexaStopAlexaJokeAudioBuffer = std::move(alexaStopAlexaJokeSds);
|
|
|
|
std::unique_ptr<AudioInputStream::Writer> alexaStopAlexaJokeAudioBufferWriter =
|
|
alexaStopAlexaJokeAudioBuffer->createWriter(avsCommon::avs::AudioInputStream::Writer::Policy::NONBLOCKABLE);
|
|
|
|
std::string audioFilePath = inputsDirPath + ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE;
|
|
bool error;
|
|
std::vector<int16_t> audioData = readAudioFromFile(audioFilePath, &error);
|
|
ASSERT_FALSE(error);
|
|
|
|
alexaStopAlexaJokeAudioBufferWriter->write(audioData.data(), audioData.size());
|
|
|
|
auto detector = SensoryKeywordDetector::create(
|
|
alexaStopAlexaJokeAudioBuffer, compatibleAudioFormat, {keyWordObserver1}, {stateObserver}, modelFilePath);
|
|
ASSERT_TRUE(detector);
|
|
|
|
// so that when we close the writer, we know for sure that the reader will be closed
|
|
auto detections =
|
|
keyWordObserver1->waitForNDetections(NUM_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE, DEFAULT_TIMEOUT);
|
|
ASSERT_EQ(detections.size(), NUM_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE);
|
|
|
|
bool stateChanged = false;
|
|
KeyWordDetectorStateObserverInterface::KeyWordDetectorState stateReceived =
|
|
stateObserver->waitForStateChange(DEFAULT_TIMEOUT, &stateChanged);
|
|
ASSERT_TRUE(stateChanged);
|
|
ASSERT_EQ(stateReceived, KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE);
|
|
|
|
alexaStopAlexaJokeAudioBufferWriter->close();
|
|
stateChanged = false;
|
|
stateReceived = stateObserver->waitForStateChange(DEFAULT_TIMEOUT, &stateChanged);
|
|
ASSERT_TRUE(stateChanged);
|
|
ASSERT_EQ(stateReceived, KeyWordDetectorStateObserverInterface::KeyWordDetectorState::STREAM_CLOSED);
|
|
}
|
|
|
|
/**
|
|
* Tests that we get back the expected number of keywords for the alexa_stop_alexa_joke.wav file for one keyword
|
|
* observer even when SDS has other data prior to the audio file in it. This tests that the reference point that the
|
|
* Sensory wrapper uses is working as expected.
|
|
*/
|
|
TEST_F(SensoryKeywordTest, getExpectedNumberOfDetectionsInAlexaStopAlexaJokeAudioFileWithRandomDataAtBeginning) {
|
|
auto alexaStopAlexaJokeBuffer = std::make_shared<avsCommon::avs::AudioInputStream::Buffer>(500000);
|
|
auto alexaStopAlexaJokeSds = avsCommon::avs::AudioInputStream::create(alexaStopAlexaJokeBuffer, 2, 1);
|
|
std::shared_ptr<AudioInputStream> alexaStopAlexaJokeAudioBuffer = std::move(alexaStopAlexaJokeSds);
|
|
|
|
std::unique_ptr<AudioInputStream::Writer> alexaStopAlexaJokeAudioBufferWriter =
|
|
alexaStopAlexaJokeAudioBuffer->createWriter(avsCommon::avs::AudioInputStream::Writer::Policy::NONBLOCKABLE);
|
|
|
|
std::string audioFilePath = inputsDirPath + ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE;
|
|
bool error;
|
|
std::vector<int16_t> audioData = readAudioFromFile(audioFilePath, &error);
|
|
ASSERT_FALSE(error);
|
|
|
|
std::vector<int16_t> randomData(5000, 0);
|
|
alexaStopAlexaJokeAudioBufferWriter->write(randomData.data(), randomData.size());
|
|
|
|
auto detector = SensoryKeywordDetector::create(
|
|
alexaStopAlexaJokeAudioBuffer, compatibleAudioFormat, {keyWordObserver1}, {stateObserver}, modelFilePath);
|
|
ASSERT_TRUE(detector);
|
|
|
|
alexaStopAlexaJokeAudioBufferWriter->write(audioData.data(), audioData.size());
|
|
|
|
auto detections =
|
|
keyWordObserver1->waitForNDetections(NUM_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE, DEFAULT_TIMEOUT);
|
|
|
|
ASSERT_EQ(detections.size(), NUM_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE);
|
|
|
|
for (unsigned int i = 0; i < END_INDICES_OF_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE.size(); ++i) {
|
|
ASSERT_TRUE(isResultPresent(
|
|
detections,
|
|
BEGIN_INDICES_OF_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE.at(i) + randomData.size(),
|
|
END_INDICES_OF_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE.at(i) + randomData.size(),
|
|
KEYWORD));
|
|
}
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace kwd
|
|
} // namespace alexaClientSDK
|
|
|
|
int main(int argc, char** argv) {
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
if (argc < 2) {
|
|
std::cerr << "USAGE: " << std::string(argv[0]) << " <path_to_inputs_folder>" << std::endl;
|
|
return 1;
|
|
} else {
|
|
alexaClientSDK::kwd::test::inputsDirPath = std::string(argv[1]);
|
|
return RUN_ALL_TESTS();
|
|
}
|
|
}
|