283 lines
9.7 KiB
C++
283 lines
9.7 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 <fstream>
|
|
#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/Logger/LoggerSinkManager.h>
|
|
#include <AndroidUtilities/AndroidLogger.h>
|
|
|
|
#include "AndroidSLESMediaPlayer/FFmpegAttachmentInputController.h"
|
|
#include "AndroidSLESMediaPlayer/FFmpegDecoder.h"
|
|
|
|
/// String to identify log entries originating from this file.
|
|
static const std::string TAG("FFmpegDecoderTest");
|
|
|
|
/**
|
|
* 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::utils::logger;
|
|
|
|
/// Configure @c MockAttachmentReader to simulate getting the entire file before being read.
|
|
static const std::vector<size_t> RECEIVE_SIZES{std::numeric_limits<size_t>::max()};
|
|
|
|
/// Test input folder.
|
|
static std::string inputFolder;
|
|
|
|
/// MP3 test file path relative to the input folder.
|
|
static const std::string MP3_FILE_PATH("/fox_dog.mp3");
|
|
|
|
/// Some arbitrary size that should fit valid audio samples.
|
|
constexpr size_t BUFFER_SIZE = 4096;
|
|
|
|
/**
|
|
* Test class for FFmpegDecoder
|
|
*/
|
|
class FFmpegDecoderTest : public Test {
|
|
protected:
|
|
void writeInput(size_t max_bytes = std::numeric_limits<size_t>::max());
|
|
|
|
void writeCorruptedInput(size_t skipInterval);
|
|
|
|
void SetUp() override {
|
|
m_inAttachment = std::make_shared<attachment::InProcessAttachment>("input");
|
|
m_attachment =
|
|
m_inAttachment->createReader(attachment::InProcessAttachmentReader::SDSTypeReader::Policy::NONBLOCKING);
|
|
m_reader = FFmpegAttachmentInputController::create(m_attachment);
|
|
m_inputFileName = inputFolder + MP3_FILE_PATH;
|
|
m_inputSize = 0;
|
|
}
|
|
|
|
void TearDown() override {
|
|
m_reader.reset();
|
|
m_inAttachment.reset();
|
|
}
|
|
|
|
/// Attachment used for the input.
|
|
std::shared_ptr<attachment::InProcessAttachment> m_inAttachment;
|
|
|
|
/// Mock the attachment reader.
|
|
std::shared_ptr<attachment::AttachmentReader> m_attachment;
|
|
|
|
/// Create an input reader.
|
|
std::unique_ptr<FFmpegAttachmentInputController> m_reader;
|
|
|
|
/// String with the input file name.
|
|
std::string m_inputFileName;
|
|
|
|
/// The amount of bytes written to the input attachment.
|
|
size_t m_inputSize;
|
|
};
|
|
|
|
void FFmpegDecoderTest::writeInput(size_t maxBytes) {
|
|
auto inputWriter = m_inAttachment->createWriter();
|
|
char buffer[BUFFER_SIZE];
|
|
std::ifstream mediaFile{m_inputFileName, std::ios_base::binary};
|
|
m_inputSize = 0;
|
|
while (!mediaFile.eof() && m_inputSize + BUFFER_SIZE < maxBytes) {
|
|
mediaFile.read(buffer, BUFFER_SIZE);
|
|
attachment::AttachmentWriter::WriteStatus status;
|
|
m_inputSize += inputWriter->write(buffer, mediaFile.gcount(), &status);
|
|
}
|
|
inputWriter->close();
|
|
}
|
|
|
|
void FFmpegDecoderTest::writeCorruptedInput(size_t skipInterval) {
|
|
auto inputWriter = m_inAttachment->createWriter();
|
|
char buffer[BUFFER_SIZE];
|
|
std::ifstream mediaFile{m_inputFileName, std::ios_base::binary};
|
|
size_t iteration = 0;
|
|
while (!mediaFile.eof()) {
|
|
iteration++;
|
|
if ((iteration % skipInterval) != 0) {
|
|
mediaFile.read(buffer, BUFFER_SIZE);
|
|
attachment::AttachmentWriter::WriteStatus status;
|
|
m_inputSize += inputWriter->write(buffer, mediaFile.gcount(), &status);
|
|
}
|
|
}
|
|
inputWriter->close();
|
|
}
|
|
|
|
/// Test decoder create.
|
|
TEST_F(FFmpegDecoderTest, testCreateSucceed) {
|
|
writeInput();
|
|
auto decoder = FFmpegDecoder::create(std::move(m_reader));
|
|
EXPECT_NE(decoder, nullptr);
|
|
}
|
|
|
|
/// Test decoder create with null reader.
|
|
TEST_F(FFmpegDecoderTest, testCreateFailedNullReader) {
|
|
writeInput();
|
|
auto decoder = FFmpegDecoder::create(nullptr);
|
|
EXPECT_EQ(decoder, nullptr);
|
|
}
|
|
|
|
/// Test decoding an entire file.
|
|
TEST_F(FFmpegDecoderTest, testDecodeFullFile) {
|
|
writeInput();
|
|
auto decoder = FFmpegDecoder::create(std::move(m_reader));
|
|
ASSERT_NE(decoder, nullptr);
|
|
|
|
FFmpegDecoder::Status status = FFmpegDecoder::Status::OK;
|
|
int16_t buffer[BUFFER_SIZE];
|
|
size_t totalWordsRead = 0;
|
|
while (status == FFmpegDecoder::Status::OK) {
|
|
size_t wordsRead;
|
|
std::tie(status, wordsRead) = decoder->read(buffer, BUFFER_SIZE);
|
|
totalWordsRead += wordsRead;
|
|
EXPECT_TRUE(status != FFmpegDecoder::Status::OK || wordsRead > 0);
|
|
}
|
|
EXPECT_EQ(status, FFmpegDecoder::Status::DONE);
|
|
EXPECT_GT(totalWordsRead * sizeof(buffer[0]), m_inputSize);
|
|
}
|
|
|
|
/// Test that it's possible to decode a file that was been truncated past the header.
|
|
TEST_F(FFmpegDecoderTest, testTruncatedInput) {
|
|
std::ifstream mediaFile{m_inputFileName, std::ios_base::binary};
|
|
mediaFile.seekg(0, std::ios_base::seek_dir::end);
|
|
writeInput(mediaFile.tellg() / 2); // Write only half of the file.
|
|
|
|
auto decoder = FFmpegDecoder::create(std::move(m_reader));
|
|
ASSERT_NE(decoder, nullptr);
|
|
|
|
FFmpegDecoder::Status status = FFmpegDecoder::Status::OK;
|
|
int16_t buffer[BUFFER_SIZE];
|
|
size_t totalWordsRead = 0;
|
|
while (status == FFmpegDecoder::Status::OK) {
|
|
size_t wordsRead;
|
|
std::tie(status, wordsRead) = decoder->read(buffer, BUFFER_SIZE);
|
|
totalWordsRead += wordsRead;
|
|
EXPECT_TRUE(status != FFmpegDecoder::Status::OK || wordsRead > 0);
|
|
}
|
|
EXPECT_EQ(status, FFmpegDecoder::Status::DONE);
|
|
EXPECT_GT(totalWordsRead * sizeof(buffer[0]), m_inputSize);
|
|
}
|
|
|
|
/// Test that the decoder recovers if the file is missing parts of it.
|
|
TEST_F(FFmpegDecoderTest, testCorruptedInput) {
|
|
constexpr size_t interval = 10; // Skip a write at this interval.
|
|
std::ifstream mediaFile{m_inputFileName, std::ios_base::binary};
|
|
mediaFile.seekg(0, std::ios_base::seek_dir::end);
|
|
writeCorruptedInput(interval); // Write file with missing bits.
|
|
|
|
auto decoder = FFmpegDecoder::create(std::move(m_reader));
|
|
ASSERT_NE(decoder, nullptr);
|
|
|
|
FFmpegDecoder::Status status = FFmpegDecoder::Status::OK;
|
|
int16_t buffer[BUFFER_SIZE];
|
|
size_t totalWordsRead = 0;
|
|
while (status == FFmpegDecoder::Status::OK) {
|
|
size_t wordsRead;
|
|
std::tie(status, wordsRead) = decoder->read(buffer, BUFFER_SIZE);
|
|
totalWordsRead += wordsRead;
|
|
EXPECT_TRUE(status != FFmpegDecoder::Status::OK || wordsRead > 0);
|
|
}
|
|
EXPECT_EQ(status, FFmpegDecoder::Status::DONE);
|
|
EXPECT_GT(totalWordsRead * sizeof(buffer[0]), m_inputSize);
|
|
}
|
|
|
|
/// Test that the decoder will error if input has invalid media.
|
|
TEST_F(FFmpegDecoderTest, testInvalidInput) {
|
|
// Create input with 0101's
|
|
std::vector<int16_t> input{BUFFER_SIZE, 0x5555};
|
|
attachment::AttachmentWriter::WriteStatus writeStatus;
|
|
auto writer = m_inAttachment->createWriter();
|
|
m_inputSize = writer->write(input.data(), BUFFER_SIZE, &writeStatus);
|
|
EXPECT_EQ(m_inputSize, BUFFER_SIZE);
|
|
|
|
auto decoder = FFmpegDecoder::create(std::move(m_reader));
|
|
ASSERT_NE(decoder, nullptr);
|
|
|
|
int16_t buffer[BUFFER_SIZE];
|
|
FFmpegDecoder::Status status;
|
|
size_t wordsRead;
|
|
std::tie(status, wordsRead) = decoder->read(buffer, BUFFER_SIZE);
|
|
|
|
EXPECT_EQ(wordsRead, 0u);
|
|
EXPECT_EQ(status, FFmpegDecoder::Status::ERROR);
|
|
|
|
writer->close();
|
|
}
|
|
|
|
/// Check that read with a buffer that is too small fails.
|
|
TEST_F(FFmpegDecoderTest, testReadSmallBuffer) {
|
|
writeInput();
|
|
auto decoder = FFmpegDecoder::create(std::move(m_reader));
|
|
ASSERT_NE(decoder, nullptr);
|
|
|
|
constexpr size_t smallBufferSize = 1; // Some arbitrary size that doesn't fit any valid frame.
|
|
int16_t buffer[smallBufferSize];
|
|
FFmpegDecoder::Status status;
|
|
size_t wordsRead;
|
|
std::tie(status, wordsRead) = decoder->read(buffer, smallBufferSize);
|
|
|
|
EXPECT_EQ(wordsRead, 0u);
|
|
EXPECT_EQ(status, FFmpegDecoder::Status::ERROR);
|
|
}
|
|
|
|
/// Check that we can abort the decoding during initialization.
|
|
TEST_F(FFmpegDecoderTest, testAbortInitialization) {
|
|
auto decoder = FFmpegDecoder::create(std::move(m_reader));
|
|
ASSERT_NE(decoder, nullptr);
|
|
|
|
std::atomic<FFmpegDecoder::Status> status{FFmpegDecoder::Status::OK};
|
|
std::thread decoderThread{[&decoder, &status]() {
|
|
int16_t buffer[BUFFER_SIZE];
|
|
size_t wordsRead;
|
|
std::tie(status, wordsRead) = decoder->read(buffer, BUFFER_SIZE);
|
|
}};
|
|
|
|
// Wait an arbitrary time before calling abort. The read should not return till abort is called.
|
|
std::chrono::milliseconds timeout{50};
|
|
std::this_thread::sleep_for(timeout);
|
|
EXPECT_EQ(status, FFmpegDecoder::Status::OK);
|
|
|
|
decoder->abort();
|
|
decoderThread.join();
|
|
EXPECT_EQ(status, FFmpegDecoder::Status::ERROR);
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace android
|
|
} // namespace mediaPlayer
|
|
} // namespace alexaClientSDK
|
|
|
|
int main(int argc, char** argv) {
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
|
|
if (argc < 2) {
|
|
std::cerr << "Usage: " << std::string(argv[0]) << " <absolute path to test inputs folder>" << std::endl;
|
|
} else {
|
|
alexaClientSDK::mediaPlayer::android::test::inputFolder = std::string(argv[1]);
|
|
return RUN_ALL_TESTS();
|
|
}
|
|
}
|