468 lines
17 KiB
C++
468 lines
17 KiB
C++
/*
|
|
* Copyright 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.
|
|
*/
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#include <memory>
|
|
#include <thread>
|
|
#include <tuple>
|
|
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <AVSCommon/AVS/Attachment/AttachmentWriter.h>
|
|
#include <AVSCommon/AVS/Attachment/InProcessAttachment.h>
|
|
#include <AVSCommon/Utils/LibcurlUtils/HTTPContentFetcherFactory.h>
|
|
#include <AVSCommon/Utils/Logger/LoggerSinkManager.h>
|
|
#include <AVSCommon/Utils/MediaPlayer/MediaPlayerObserverInterface.h>
|
|
#include <AndroidSLESMediaPlayer/AndroidSLESMediaPlayer.h>
|
|
#include <AndroidSLESMediaPlayer/FFmpegDecoder.h>
|
|
#include <AndroidUtilities/AndroidLogger.h>
|
|
#include <AndroidUtilities/AndroidSLESEngine.h>
|
|
#include <Audio/Data/med_alerts_notification_01._TTH_.mp3.h>
|
|
#include <Audio/Data/med_system_alerts_melodic_01_short._TTH_.wav.h>
|
|
|
|
/// String to identify log entries originating from this file.
|
|
static const std::string TAG("AndroidSLESMediaPlayerTest");
|
|
|
|
/**
|
|
* Create a LogEntry using this file's TAG and the specified event string.
|
|
*
|
|
* @param The event string for this @c LogEntry.
|
|
*/
|
|
#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event)
|
|
|
|
namespace alexaClientSDK {
|
|
namespace mediaPlayer {
|
|
namespace android {
|
|
namespace test {
|
|
|
|
using namespace ::testing;
|
|
using namespace avsCommon::avs;
|
|
using namespace avsCommon::avs::attachment;
|
|
using namespace avsCommon::sdkInterfaces;
|
|
using namespace avsCommon::utils::logger;
|
|
using namespace avsCommon::utils::mediaPlayer;
|
|
using namespace applicationUtilities::androidUtilities;
|
|
|
|
/// The size of the input buffer.
|
|
static constexpr size_t MP3_INPUT_SIZE =
|
|
applicationUtilities::resources::audio::data::med_alerts_notification_01__TTH__mp3_len;
|
|
|
|
/// An input buffer with an mp3 file.
|
|
static const auto MP3_INPUT_CSTR = applicationUtilities::resources::audio::data::med_alerts_notification_01__TTH__mp3;
|
|
|
|
/// The mp3 duration in milliseconds.
|
|
static const std::chrono::milliseconds MP3_INPUT_DURATION{1440};
|
|
|
|
/// The size of the input buffer.
|
|
static const size_t RAW_INPUT_SIZE =
|
|
applicationUtilities::resources::audio::data::med_system_alerts_melodic_01_short__TTH__wav_len;
|
|
|
|
/// An input buffer with an mp3 file.
|
|
static const auto RAW_INPUT_CSTR =
|
|
applicationUtilities::resources::audio::data::med_system_alerts_melodic_01_short__TTH__wav;
|
|
|
|
/// This class mocks an attachment reader that reads from the @c INPUT_CSTR.
|
|
class MockAttachmentReader : public AttachmentReader {
|
|
public:
|
|
MOCK_METHOD1(seek, bool(uint64_t offset));
|
|
MOCK_CONST_METHOD0(tell, uint64_t());
|
|
MOCK_METHOD0(getNumUnreadBytes, uint64_t());
|
|
MOCK_METHOD1(close, void(ClosePoint closePoint));
|
|
|
|
/**
|
|
* Method that simulates files reading.
|
|
*
|
|
* @param buf The buffer to read into.
|
|
* @param numBytes The number of bytes to read.
|
|
* @param readStatus[out] The status from the last read.
|
|
* @param timeoutMs The timeout used for the read operation.
|
|
* @return The number of bytes read.
|
|
*/
|
|
size_t read(
|
|
void* buf,
|
|
std::size_t numBytes,
|
|
AttachmentReader::ReadStatus* readStatus,
|
|
std::chrono::milliseconds timeoutMs);
|
|
|
|
/**
|
|
* @c MockAttachmentReader constructor.
|
|
*
|
|
* @param input The buffer that holds the input data.
|
|
* @param length The @c input buffer length.
|
|
* @param timeoutIteration Optional iteration that should trigger a timeout response.
|
|
*/
|
|
MockAttachmentReader(const unsigned char* input, size_t length, ssize_t timeoutIteration = -1);
|
|
|
|
private:
|
|
/// Save the index for the last read.
|
|
size_t m_index;
|
|
|
|
/// Buffer used to read the input data from.
|
|
const unsigned char* m_input;
|
|
|
|
/// The length of the input data.
|
|
size_t m_length;
|
|
|
|
/// Read iterations counter.
|
|
ssize_t m_iteration;
|
|
|
|
/// This can be used to trigger a timeout response when the iteration reaches the timeout iteration.
|
|
ssize_t m_timeoutIteration;
|
|
};
|
|
|
|
/// Class that can be used to wait for an event.
|
|
class WaitEvent {
|
|
public:
|
|
/// Wake up a thread that is waiting for this event.
|
|
void wakeUp();
|
|
|
|
/// The default timeout for an expected event.
|
|
static const std::chrono::seconds DEFAULT_TIMEOUT;
|
|
|
|
/**
|
|
* Wait for wake up event.
|
|
*
|
|
* @param timeout The maximum amount of time to wait for the event.
|
|
*/
|
|
std::cv_status wait(const std::chrono::milliseconds& timeout = DEFAULT_TIMEOUT);
|
|
|
|
private:
|
|
/// The condition variable used to wake up the thread that is waiting.
|
|
std::condition_variable m_condition;
|
|
};
|
|
|
|
/// Mocks the content fetcher factory.
|
|
class MockContentFetcherFactory : public HTTPContentFetcherInterfaceFactoryInterface {
|
|
public:
|
|
MOCK_METHOD1(create, std::unique_ptr<HTTPContentFetcherInterface>(const std::string& url));
|
|
};
|
|
|
|
/// Mocks the media player observer.
|
|
class MockObserver : public MediaPlayerObserverInterface {
|
|
public:
|
|
MOCK_METHOD1(onPlaybackStarted, void(SourceId));
|
|
MOCK_METHOD1(onPlaybackFinished, void(SourceId));
|
|
MOCK_METHOD1(onPlaybackStopped, void(SourceId));
|
|
MOCK_METHOD1(onPlaybackPaused, void(SourceId));
|
|
MOCK_METHOD1(onPlaybackResumed, void(SourceId));
|
|
MOCK_METHOD3(onPlaybackError, void(SourceId, const ErrorType&, std::string));
|
|
|
|
MOCK_METHOD1(onBufferRefilled, void(SourceId));
|
|
MOCK_METHOD1(onBufferUnderrun, void(SourceId));
|
|
};
|
|
|
|
/**
|
|
* Test class for @c AndroidSLESMediaPlayer
|
|
*/
|
|
class AndroidSLESMediaPlayerTest : public Test {
|
|
protected:
|
|
void SetUp() override {
|
|
m_reader = std::make_shared<MockAttachmentReader>(MP3_INPUT_CSTR, MP3_INPUT_SIZE);
|
|
m_engine = AndroidSLESEngine::create();
|
|
auto factory = std::make_shared<MockContentFetcherFactory>();
|
|
m_player =
|
|
AndroidSLESMediaPlayer::create(factory, m_engine, SpeakerInterface::Type::AVS_SPEAKER_VOLUME, "Player");
|
|
m_observer = std::make_shared<NiceMock<MockObserver>>();
|
|
m_player->setObserver(m_observer);
|
|
}
|
|
|
|
std::shared_ptr<std::stringstream> createStream() {
|
|
auto stream = std::make_shared<std::stringstream>();
|
|
stream->write(reinterpret_cast<const char*>(MP3_INPUT_CSTR), MP3_INPUT_SIZE);
|
|
return stream;
|
|
}
|
|
|
|
void TearDown() override {
|
|
if (m_player) {
|
|
m_player->shutdown();
|
|
m_player.reset();
|
|
}
|
|
m_reader.reset();
|
|
}
|
|
|
|
/// We need to instantiate a player in order to use AMedia* functionalitites.
|
|
std::shared_ptr<AndroidSLESMediaPlayer> m_player;
|
|
|
|
/// Mock the attachment reader.
|
|
std::shared_ptr<MockAttachmentReader> m_reader;
|
|
|
|
/// Mock a media player observer.
|
|
std::shared_ptr<MockObserver> m_observer;
|
|
|
|
/// Keep a pointer to the engine.
|
|
std::shared_ptr<AndroidSLESEngine> m_engine;
|
|
};
|
|
|
|
/// Test create with null factory.
|
|
TEST_F(AndroidSLESMediaPlayerTest, testCreateNullFactory) {
|
|
auto player =
|
|
AndroidSLESMediaPlayer::create(nullptr, m_engine, SpeakerInterface::Type::AVS_SPEAKER_VOLUME, "player");
|
|
EXPECT_EQ(player, nullptr);
|
|
}
|
|
|
|
/// Test create with null engine.
|
|
TEST_F(AndroidSLESMediaPlayerTest, testCreateNullEngine) {
|
|
auto factory = std::make_shared<MockContentFetcherFactory>();
|
|
auto player =
|
|
AndroidSLESMediaPlayer::create(factory, nullptr, SpeakerInterface::Type::AVS_SPEAKER_VOLUME, "player");
|
|
EXPECT_EQ(player, nullptr);
|
|
}
|
|
|
|
/// Test buffer queue with media player.
|
|
TEST_F(AndroidSLESMediaPlayerTest, testBQMediaPlayer) {
|
|
auto id = m_player->setSource(m_reader, nullptr);
|
|
|
|
WaitEvent finishedEvent;
|
|
EXPECT_CALL(*m_observer, onPlaybackStarted(id)).Times(1);
|
|
EXPECT_CALL(*m_observer, onPlaybackFinished(id)).WillOnce(InvokeWithoutArgs([&finishedEvent]() {
|
|
finishedEvent.wakeUp();
|
|
}));
|
|
|
|
EXPECT_TRUE(m_player->play(id));
|
|
EXPECT_EQ(finishedEvent.wait(), std::cv_status::no_timeout);
|
|
}
|
|
|
|
/// Test buffer queue with media player and raw file.
|
|
TEST_F(AndroidSLESMediaPlayerTest, testBQRawMediaPlayer) {
|
|
auto format = avsCommon::utils::AudioFormat{.dataSigned = true,
|
|
.numChannels = 2,
|
|
.sampleSizeInBits = 16,
|
|
.sampleRateHz = 48000,
|
|
.endianness = avsCommon::utils::AudioFormat::Endianness::LITTLE,
|
|
.encoding = avsCommon::utils::AudioFormat::Encoding::LPCM};
|
|
m_reader = std::make_shared<MockAttachmentReader>(RAW_INPUT_CSTR, RAW_INPUT_SIZE);
|
|
auto id = m_player->setSource(m_reader, &format);
|
|
|
|
WaitEvent finishedEvent;
|
|
EXPECT_CALL(*m_observer, onPlaybackStarted(id)).Times(1);
|
|
EXPECT_CALL(*m_observer, onPlaybackFinished(id)).WillOnce(InvokeWithoutArgs([&finishedEvent]() {
|
|
finishedEvent.wakeUp();
|
|
}));
|
|
EXPECT_TRUE(m_player->play(id));
|
|
EXPECT_EQ(finishedEvent.wait(), std::cv_status::no_timeout);
|
|
}
|
|
|
|
/// Test that media is played correct even if a timeout happens in the first read.
|
|
TEST_F(AndroidSLESMediaPlayerTest, testFirstReadTimeout) {
|
|
// This read iteration indicates the first read call.
|
|
static const ssize_t firstIteration = 0;
|
|
m_reader = std::make_shared<MockAttachmentReader>(MP3_INPUT_CSTR, MP3_INPUT_SIZE, firstIteration);
|
|
auto id = m_player->setSource(m_reader, nullptr);
|
|
|
|
WaitEvent finishedEvent;
|
|
EXPECT_CALL(*m_observer, onPlaybackStarted(id)).Times(1);
|
|
EXPECT_CALL(*m_observer, onPlaybackFinished(id)).WillOnce(InvokeWithoutArgs([&finishedEvent]() {
|
|
finishedEvent.wakeUp();
|
|
}));
|
|
EXPECT_TRUE(m_player->play(id));
|
|
EXPECT_EQ(finishedEvent.wait(), std::cv_status::no_timeout);
|
|
EXPECT_EQ(m_player->getOffset(id), MP3_INPUT_DURATION);
|
|
}
|
|
|
|
/// Test that media is played correct even after a timeout during initialization.
|
|
TEST_F(AndroidSLESMediaPlayerTest, testInitializeTimeout) {
|
|
// This read iteration occurs during decoder initialization.
|
|
static const ssize_t initializationIteration = 1;
|
|
m_reader = std::make_shared<MockAttachmentReader>(MP3_INPUT_CSTR, MP3_INPUT_SIZE, initializationIteration);
|
|
auto id = m_player->setSource(m_reader, nullptr);
|
|
|
|
WaitEvent finishedEvent;
|
|
EXPECT_CALL(*m_observer, onPlaybackStarted(id)).Times(1);
|
|
EXPECT_CALL(*m_observer, onPlaybackFinished(id)).WillOnce(InvokeWithoutArgs([&finishedEvent]() {
|
|
finishedEvent.wakeUp();
|
|
}));
|
|
EXPECT_TRUE(m_player->play(id));
|
|
EXPECT_EQ(finishedEvent.wait(), std::cv_status::no_timeout);
|
|
EXPECT_EQ(m_player->getOffset(id), MP3_INPUT_DURATION);
|
|
}
|
|
|
|
/// Test that media is played correct even after a timeout during decoding.
|
|
TEST_F(AndroidSLESMediaPlayerTest, testDecodingTimeout) {
|
|
// This read iteration occurs during decoding state.
|
|
const ssize_t decodeIteration = 10;
|
|
m_reader = std::make_shared<MockAttachmentReader>(MP3_INPUT_CSTR, MP3_INPUT_SIZE, decodeIteration);
|
|
auto id = m_player->setSource(m_reader, nullptr);
|
|
|
|
WaitEvent finishedEvent;
|
|
EXPECT_CALL(*m_observer, onPlaybackStarted(id)).Times(1);
|
|
EXPECT_CALL(*m_observer, onPlaybackFinished(id)).WillOnce(InvokeWithoutArgs([&finishedEvent]() {
|
|
finishedEvent.wakeUp();
|
|
}));
|
|
EXPECT_TRUE(m_player->play(id));
|
|
EXPECT_EQ(finishedEvent.wait(), std::cv_status::no_timeout);
|
|
EXPECT_EQ(m_player->getOffset(id), MP3_INPUT_DURATION);
|
|
}
|
|
|
|
/// Test media player and istream source.
|
|
TEST_F(AndroidSLESMediaPlayerTest, testStreamMediaPlayer) {
|
|
auto id = m_player->setSource(createStream(), false);
|
|
|
|
WaitEvent finishedEvent;
|
|
EXPECT_CALL(*m_observer, onPlaybackStarted(id)).Times(1);
|
|
EXPECT_CALL(*m_observer, onPlaybackFinished(id)).WillOnce(InvokeWithoutArgs([&finishedEvent]() {
|
|
finishedEvent.wakeUp();
|
|
}));
|
|
|
|
EXPECT_TRUE(m_player->play(id));
|
|
EXPECT_EQ(finishedEvent.wait(), std::cv_status::no_timeout);
|
|
}
|
|
|
|
/// Test media player, istream source and repeat on.
|
|
TEST_F(AndroidSLESMediaPlayerTest, testStreamRepeatMediaPlayer) {
|
|
auto repeat = true;
|
|
auto id = m_player->setSource(createStream(), repeat);
|
|
|
|
EXPECT_CALL(*m_observer, onPlaybackStarted(id)).Times(1);
|
|
EXPECT_CALL(*m_observer, onPlaybackStopped(id)).Times(1);
|
|
EXPECT_TRUE(m_player->play(id));
|
|
|
|
std::chrono::milliseconds sleepPeriod{100};
|
|
std::this_thread::sleep_for(sleepPeriod);
|
|
EXPECT_TRUE(m_player->stop(id));
|
|
}
|
|
|
|
/// Test media player pause / resume.
|
|
TEST_F(AndroidSLESMediaPlayerTest, testResumeMediaPlayer) {
|
|
auto repeat = true;
|
|
auto id = m_player->setSource(createStream(), repeat);
|
|
|
|
EXPECT_CALL(*m_observer, onPlaybackStarted(id)).Times(1);
|
|
EXPECT_CALL(*m_observer, onPlaybackStopped(id)).Times(1);
|
|
EXPECT_CALL(*m_observer, onPlaybackPaused(id)).Times(1);
|
|
EXPECT_CALL(*m_observer, onPlaybackResumed(id)).Times(1);
|
|
EXPECT_TRUE(m_player->play(id));
|
|
|
|
std::chrono::milliseconds sleepPeriod{100};
|
|
std::this_thread::sleep_for(sleepPeriod);
|
|
EXPECT_TRUE(m_player->pause(id));
|
|
|
|
std::this_thread::sleep_for(sleepPeriod);
|
|
EXPECT_TRUE(m_player->resume(id));
|
|
|
|
std::this_thread::sleep_for(sleepPeriod);
|
|
EXPECT_TRUE(m_player->stop(id));
|
|
}
|
|
|
|
/// Test play fails with wrong id.
|
|
TEST_F(AndroidSLESMediaPlayerTest, testPlayFailed) {
|
|
auto id = m_player->setSource(m_reader, nullptr);
|
|
EXPECT_CALL(*m_observer, onPlaybackStarted(_)).Times(0);
|
|
EXPECT_FALSE(m_player->play(id + 1));
|
|
}
|
|
|
|
/// Test pause fails with wrong id.
|
|
TEST_F(AndroidSLESMediaPlayerTest, testPauseFailed) {
|
|
auto id = m_player->setSource(m_reader, nullptr);
|
|
EXPECT_CALL(*m_observer, onPlaybackStarted(id)).Times(1);
|
|
EXPECT_CALL(*m_observer, onPlaybackPaused(_)).Times(0);
|
|
EXPECT_TRUE(m_player->play(id));
|
|
EXPECT_FALSE(m_player->pause(id + 1));
|
|
EXPECT_TRUE(m_player->stop(id));
|
|
}
|
|
|
|
/// Test pause fails if not playing.
|
|
TEST_F(AndroidSLESMediaPlayerTest, testPauseFailedNotPlaying) {
|
|
auto id = m_player->setSource(m_reader, nullptr);
|
|
EXPECT_CALL(*m_observer, onPlaybackStarted(id)).Times(0);
|
|
EXPECT_CALL(*m_observer, onPlaybackPaused(_)).Times(0);
|
|
EXPECT_FALSE(m_player->pause(id));
|
|
}
|
|
|
|
/// Test resume fails after stop.
|
|
TEST_F(AndroidSLESMediaPlayerTest, testResumeFailedAfterStop) {
|
|
auto id = m_player->setSource(m_reader, nullptr);
|
|
EXPECT_CALL(*m_observer, onPlaybackStarted(id)).Times(1);
|
|
EXPECT_CALL(*m_observer, onPlaybackStopped(id)).Times(1);
|
|
EXPECT_CALL(*m_observer, onPlaybackPaused(_)).Times(0);
|
|
EXPECT_TRUE(m_player->play(id));
|
|
EXPECT_TRUE(m_player->stop(id));
|
|
EXPECT_FALSE(m_player->resume(id));
|
|
}
|
|
|
|
/// Test stop fails with wrong id.
|
|
TEST_F(AndroidSLESMediaPlayerTest, testStopFailed) {
|
|
auto id = m_player->setSource(m_reader, nullptr);
|
|
auto fakeId = id + 1;
|
|
EXPECT_CALL(*m_observer, onPlaybackStarted(id)).Times(1);
|
|
EXPECT_CALL(*m_observer, onPlaybackStopped(_)).Times(AtLeast(1));
|
|
EXPECT_CALL(*m_observer, onPlaybackStopped(fakeId)).Times(0);
|
|
EXPECT_TRUE(m_player->play(id));
|
|
EXPECT_FALSE(m_player->stop(fakeId));
|
|
EXPECT_TRUE(m_player->stop(id));
|
|
}
|
|
|
|
/// Test get offset.
|
|
TEST_F(AndroidSLESMediaPlayerTest, testGetOffset) {
|
|
auto id = m_player->setSource(m_reader, nullptr);
|
|
|
|
WaitEvent finishedEvent;
|
|
EXPECT_CALL(*m_observer, onPlaybackStarted(id)).Times(1);
|
|
EXPECT_CALL(*m_observer, onPlaybackFinished(id)).WillOnce(InvokeWithoutArgs([&finishedEvent]() {
|
|
finishedEvent.wakeUp();
|
|
}));
|
|
|
|
EXPECT_TRUE(m_player->play(id));
|
|
EXPECT_EQ(finishedEvent.wait(), std::cv_status::no_timeout);
|
|
EXPECT_EQ(m_player->getOffset(id), MP3_INPUT_DURATION);
|
|
}
|
|
|
|
size_t MockAttachmentReader::read(
|
|
void* buf,
|
|
std::size_t numBytes,
|
|
AttachmentReader::ReadStatus* readStatus,
|
|
std::chrono::milliseconds timeoutMs) {
|
|
m_iteration++;
|
|
if (m_iteration == m_timeoutIteration) {
|
|
(*readStatus) = AttachmentReader::ReadStatus::OK_WOULDBLOCK;
|
|
return 0;
|
|
}
|
|
|
|
if (m_index < m_length) {
|
|
numBytes = std::min(numBytes, m_length - m_index);
|
|
memcpy(buf, &m_input[m_index], numBytes);
|
|
(*readStatus) = AttachmentReader::ReadStatus::OK;
|
|
m_index += numBytes;
|
|
return numBytes;
|
|
}
|
|
(*readStatus) = AttachmentReader::ReadStatus::CLOSED;
|
|
return 0;
|
|
}
|
|
|
|
MockAttachmentReader::MockAttachmentReader(const unsigned char* input, size_t length, ssize_t timeoutIteration) :
|
|
m_index{0},
|
|
m_input{input},
|
|
m_length{length},
|
|
m_iteration{-1},
|
|
m_timeoutIteration{timeoutIteration} {
|
|
}
|
|
|
|
const std::chrono::seconds WaitEvent::DEFAULT_TIMEOUT{5};
|
|
|
|
void WaitEvent::wakeUp() {
|
|
m_condition.notify_one();
|
|
}
|
|
|
|
std::cv_status WaitEvent::wait(const std::chrono::milliseconds& timeout) {
|
|
std::mutex mutex;
|
|
std::unique_lock<std::mutex> lock{mutex};
|
|
return m_condition.wait_for(lock, timeout);
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace android
|
|
} // namespace mediaPlayer
|
|
} // namespace alexaClientSDK
|