LicenseRef-.amazon.com.-ASL-1.0
Identifier: LicenseRef-.amazon.com.-ASL-1.0_alexa-auto-sdk_v1.6.2
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* SPDX-License-Identifier: LicenseRef-.amazon.com.-ASL-1.0
*
* Licensed under the Amazon Software License (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/asl/
*
* 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.
*/
#include <climits>
#include <AACE/Engine/Core/EngineMacros.h>
#include "LoopbackDetector.h"
namespace aace {
namespace engine {
namespace loopbackDetector {
/// The maximum number of readers of the stream.
static const size_t MAX_READERS = 2;
/// The amount of audio data to keep in the ring buffer.
static const std::chrono::seconds AMOUNT_OF_AUDIO_DATA_IN_BUFFER = std::chrono::seconds(5);
// String to identify log entries originating from this file.
static const std::string TAG("aace.alexa.LoopbackDetector");
LoopbackDetector::LoopbackDetector(const alexaClientSDK::avsCommon::utils::AudioFormat& audioFormat) :
alexaClientSDK::avsCommon::utils::RequiresShutdown(TAG),
m_audioFormat(audioFormat),
m_wordSize(audioFormat.sampleSizeInBits / CHAR_BIT) {
}
bool LoopbackDetector::initialize(
const std::string& defaultLocale,
std::shared_ptr<audio::AudioManagerInterface> audioManager,
std::shared_ptr<alexa::WakewordEngineAdapter> wakewordEngineAdapter) {
try {
ThrowIfNull(audioManager, "invalidAudioManager");
// create the audio channel
m_audioInputChannel = audioManager->openAudioInputChannel(
"LoopbackDetector", audio::AudioManagerInterface::AudioInputType::LOOPBACK);
ThrowIfNull(m_audioInputChannel, "invalidAudioInputChannel");
ThrowIfNot(initializeAudioInputStream(), "initializeAudioInputStreamFailed");
m_wakewordEngineAdapter = wakewordEngineAdapter;
ThrowIfNull(m_wakewordEngineAdapter, "invalidWakewordEngineAdapter");
ThrowIfNot(
m_wakewordEngineAdapter->initialize(defaultLocale, m_audioInputStream, m_audioFormat),
"wakewordInitializeFailed");
m_wakewordEngineAdapter->addKeyWordObserver(shared_from_this());
// Enable WW
ThrowIfNot(m_wakewordEngineAdapter->enable(), "enableFailed");
// tell the platform interface to start providing audio input
ThrowIfNot(startAudioInput(), "platformStartAudioInputFailed");
return true;
} catch (std::exception& ex) {
AACE_ERROR(LX(TAG, "initialize").d("reason", ex.what()));
return false;
}
}
std::shared_ptr<LoopbackDetector> LoopbackDetector::create(
const std::string& defaultLocale,
const alexaClientSDK::avsCommon::utils::AudioFormat& audioFormat,
std::shared_ptr<audio::AudioManagerInterface> audioManager,
std::shared_ptr<alexa::WakewordEngineAdapter> wakewordEngineAdapter) {
std::shared_ptr<LoopbackDetector> loopbackDetector = nullptr;
try {
loopbackDetector = std::shared_ptr<LoopbackDetector>(new LoopbackDetector(audioFormat));
ThrowIfNot(
loopbackDetector->initialize(defaultLocale, audioManager, wakewordEngineAdapter),
"initializeLoopbackDetectorFailed");
return loopbackDetector;
} catch (std::exception& ex) {
AACE_ERROR(LX(TAG, "create").d("reason", ex.what()));
if (loopbackDetector != nullptr) {
loopbackDetector->shutdown();
}
return nullptr;
}
}
void LoopbackDetector::doShutdown() {
if (m_audioInputWriter != nullptr) {
m_audioInputWriter->close();
m_audioInputWriter.reset();
}
if (m_wakewordEngineAdapter != nullptr) {
m_wakewordEngineAdapter->disable();
m_wakewordEngineAdapter->removeKeyWordObserver(shared_from_this());
m_wakewordEngineAdapter.reset();
}
}
bool LoopbackDetector::initializeAudioInputStream() {
try {
size_t size = alexaClientSDK::avsCommon::avs::AudioInputStream::calculateBufferSize(
m_audioFormat.sampleRateHz * AMOUNT_OF_AUDIO_DATA_IN_BUFFER.count(), m_wordSize, MAX_READERS);
auto buffer = std::make_shared<alexaClientSDK::avsCommon::avs::AudioInputStream::Buffer>(size);
ThrowIfNull(buffer, "couldNotCreateAudioInputBuffer");
// create the audio input stream
m_audioInputStream = alexaClientSDK::avsCommon::avs::AudioInputStream::create(buffer, m_wordSize, MAX_READERS);
ThrowIfNull(m_audioInputStream, "couldNotCreateAudioInputStream");
// create the audio input writer
m_audioInputWriter = m_audioInputStream->createWriter(
alexaClientSDK::avsCommon::avs::AudioInputStream::Writer::Policy::NONBLOCKABLE);
ThrowIfNull(m_audioInputWriter, "couldNotCreateAudioInputWriter");
return true;
} catch (std::exception& ex) {
AACE_ERROR(LX(TAG, "initializeAudioInputStream").d("reason", ex.what()));
m_audioInputStream.reset();
m_audioInputWriter.reset();
return false;
}
}
bool LoopbackDetector::startAudioInput() {
try {
std::weak_ptr<LoopbackDetector> wp = shared_from_this();
m_currentChannelId = m_audioInputChannel->start([wp](const int16_t* data, const size_t size) {
if (auto sp = wp.lock()) {
sp->write(data, size);
} else {
AACE_ERROR(LX(TAG, "startAudioInput").d("reason", "invalidWeakPtrReference"));
}
});
// throw an exception if we failed to start the audio input channel
ThrowIf(
m_currentChannelId == audio::AudioInputChannelInterface::INVALID_CHANNEL, "audioInputChannelStartFailed");
return true;
} catch (std::exception& ex) {
AACE_ERROR(LX(TAG, "startAudioInput").d("reason", ex.what()));
return false;
}
}
bool LoopbackDetector::stopAudioInput() {
try {
ThrowIf(m_currentChannelId == audio::AudioInputChannelInterface::INVALID_CHANNEL, "invalidAudioChannelId");
m_audioInputChannel->stop(m_currentChannelId);
// reset the channel id
m_currentChannelId = audio::AudioInputChannelInterface::INVALID_CHANNEL;
return true;
} catch (std::exception& ex) {
AACE_ERROR(LX(TAG, "stopAudioInput").d("reason", ex.what()));
m_currentChannelId = audio::AudioInputChannelInterface::INVALID_CHANNEL;
return false;
}
}
ssize_t LoopbackDetector::write(const int16_t* data, const size_t size) {
try {
ThrowIfNull(m_audioInputWriter, "nullAudioInputWriter");
ssize_t result = m_audioInputWriter->write(data, size);
ThrowIf(result < 0, "errorWritingData");
return result;
} catch (std::exception& ex) {
AACE_ERROR(LX(TAG, "write").d("reason", ex.what()));
return -1;
}
}
bool LoopbackDetector::shouldBlock(const std::string& wakeword, const std::chrono::milliseconds& timeout) {
std::unique_lock<std::mutex> lock(m_detectionMutex);
AACE_DEBUG(LX(TAG, "shouldBlock").d("wakeword", wakeword));
// Does wake-word is already detected by secondary WW engine within past N ms?
if ((std::chrono::system_clock::now() - m_lastDetection) < timeout) {
return true;
}
// Wait for N ms until secondary WW engine detects wake-word
m_detectionCV.wait_until(lock, std::chrono::system_clock::now() + timeout);
// Return true if wake-word is detected within N ms
return (std::chrono::system_clock::now() - m_lastDetection) < timeout;
}
void LoopbackDetector::onKeyWordDetected(
std::shared_ptr<alexaClientSDK::avsCommon::avs::AudioInputStream> stream,
std::string keyword,
alexaClientSDK::avsCommon::avs::AudioInputStream::Index beginIndex,
alexaClientSDK::avsCommon::avs::AudioInputStream::Index endIndex,
std::shared_ptr<const std::vector<char>> KWDMetadata) {
AACE_DEBUG(LX(TAG, "onKeyWordDetected").d("keyword", keyword));
// Update the last detection time
m_lastDetection = std::chrono::system_clock::now();
// Notify other threads
m_detectionCV.notify_all();
}
} // namespace loopbackDetector
} // namespace engine
} // namespace aace
NOTICE
Alexa Auto - Wake-word Loopback detection extension
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
previous
next