blob: d142781c81b3a7796531e8482a819bb936ce3090 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* 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.
*/
//#define LOG_NDEBUG 0
#define LOG_TAG "OpusHeaderTest"
#include <utils/Log.h>
#include <fstream>
#include <stdio.h>
#include <string.h>
#include <media/stagefright/foundation/OpusHeader.h>
#include "OpusHeaderTestEnvironment.h"
using namespace android;
#define OUTPUT_FILE_NAME "/data/local/tmp/OpusOutput"
// Opus in WebM is a well-known, yet under-documented, format. The codec private data
// of the track is an Opus Ogg header (https://tools.ietf.org/html/rfc7845#section-5.1)
// channel mapping offset in opus header
constexpr size_t kOpusHeaderStreamMapOffset = 21;
constexpr size_t kMaxOpusHeaderSize = 100;
// AOPUSHDR + AOPUSHDRLength +
// (8 + 8 ) +
// Header(csd) + num_streams + num_coupled + 1
// (19 + 1 + 1 + 1) +
// AOPUSDLY + AOPUSDLYLength + DELAY + AOPUSPRL + AOPUSPRLLength + PRL
// (8 + 8 + 8 + 8 + 8 + 8)
// = 86
constexpr size_t kOpusHeaderChannelMapOffset = 86;
constexpr uint32_t kOpusSampleRate = 48000;
constexpr uint64_t kOpusSeekPrerollNs = 80000000;
constexpr int64_t kNsecPerSec = 1000000000ll;
// Opus uses Vorbis channel mapping, and Vorbis channel mapping specifies
// mappings for up to 8 channels. This information is part of the Vorbis I
// Specification:
// http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html
constexpr int kMaxChannels = 8;
constexpr uint8_t kOpusChannelMap[kMaxChannels][kMaxChannels] = {
{0},
{0, 1},
{0, 2, 1},
{0, 1, 2, 3},
{0, 4, 1, 2, 3},
{0, 4, 1, 2, 3, 5},
{0, 4, 1, 2, 3, 5, 6},
{0, 6, 1, 2, 3, 4, 5, 7},
};
static OpusHeaderTestEnvironment *gEnv = nullptr;
class OpusHeaderTest {
public:
OpusHeaderTest() : mInputBuffer(nullptr) {}
~OpusHeaderTest() {
if (mEleStream.is_open()) mEleStream.close();
if (mInputBuffer) {
free(mInputBuffer);
mInputBuffer = nullptr;
}
}
ifstream mEleStream;
uint8_t *mInputBuffer;
};
class OpusHeaderParseTest : public OpusHeaderTest,
public ::testing::TestWithParam<
tuple<string /* InputFileName */, int32_t /* ChannelCount */,
bool /* isHeaderValid */, bool /* isCodecDelayValid */,
bool /* isSeekPreRollValid */, bool /* isInputValid */>> {
};
class OpusHeaderWriteTest
: public OpusHeaderTest,
public ::testing::TestWithParam<pair<int32_t /* ChannelCount */, int32_t /* skipSamples */>> {
};
TEST_P(OpusHeaderWriteTest, WriteTest) {
OpusHeader writtenHeader;
memset(&writtenHeader, 0, sizeof(writtenHeader));
int32_t channels = GetParam().first;
writtenHeader.channels = channels;
writtenHeader.num_streams = channels;
writtenHeader.channel_mapping = ((channels > 8) ? 255 : (channels > 2));
int32_t skipSamples = GetParam().second;
writtenHeader.skip_samples = skipSamples;
uint64_t codecDelayNs = skipSamples * kNsecPerSec / kOpusSampleRate;
uint8_t headerData[kMaxOpusHeaderSize];
int32_t headerSize = WriteOpusHeaders(writtenHeader, kOpusSampleRate, headerData,
sizeof(headerData), codecDelayNs, kOpusSeekPrerollNs);
ASSERT_GT(headerSize, 0) << "failed to generate Opus header";
ASSERT_LE(headerSize, kMaxOpusHeaderSize)
<< "Invalid header written. Header size can't exceed kMaxOpusHeaderSize";
ofstream ostrm;
ostrm.open(OUTPUT_FILE_NAME, ofstream::binary);
ASSERT_TRUE(ostrm.is_open()) << "Failed to open output file " << OUTPUT_FILE_NAME;
// TODO : Validate bitstream (b/150116402)
ostrm.write(reinterpret_cast<char *>(headerData), sizeof(headerData));
ostrm.close();
size_t opusHeadSize = 0;
size_t codecDelayBufSize = 0;
size_t seekPreRollBufSize = 0;
void *opusHeadBuf = nullptr;
void *codecDelayBuf = nullptr;
void *seekPreRollBuf = nullptr;
bool status = GetOpusHeaderBuffers(headerData, headerSize, &opusHeadBuf, &opusHeadSize,
&codecDelayBuf, &codecDelayBufSize, &seekPreRollBuf,
&seekPreRollBufSize);
ASSERT_TRUE(status) << "Encountered error in GetOpusHeaderBuffers";
uint64_t value = *((uint64_t *)codecDelayBuf);
ASSERT_EQ(value, codecDelayNs);
value = *((uint64_t *)seekPreRollBuf);
ASSERT_EQ(value, kOpusSeekPrerollNs);
OpusHeader parsedHeader;
status = ParseOpusHeader((uint8_t *)opusHeadBuf, opusHeadSize, &parsedHeader);
ASSERT_TRUE(status) << "Encountered error while Parsing Opus Header.";
ASSERT_EQ(writtenHeader.channels, parsedHeader.channels)
<< "Invalid header generated. Mismatch between channel counts";
ASSERT_EQ(writtenHeader.skip_samples, parsedHeader.skip_samples)
<< "Mismatch between no of skipSamples written "
"and no of skipSamples got after parsing";
ASSERT_EQ(writtenHeader.channel_mapping, parsedHeader.channel_mapping)
<< "Mismatch between channelMapping written "
"and channelMapping got after parsing";
if (parsedHeader.channel_mapping) {
ASSERT_GT(parsedHeader.channels, 2);
ASSERT_EQ(writtenHeader.num_streams, parsedHeader.num_streams)
<< "Invalid header generated. Mismatch between channel counts";
ASSERT_EQ(writtenHeader.num_coupled, parsedHeader.num_coupled)
<< "Invalid header generated. Mismatch between channel counts";
ASSERT_EQ(parsedHeader.num_coupled + parsedHeader.num_streams, parsedHeader.channels);
ASSERT_LE(parsedHeader.num_coupled, parsedHeader.num_streams)
<< "Invalid header generated. Number of coupled streams cannot be greater than "
"number "
"of streams.";
ASSERT_EQ(headerSize, kOpusHeaderChannelMapOffset + writtenHeader.channels)
<< "Invalid header written. Header size should be equal to 86 + "
"writtenHeader.channels";
uint8_t mappedChannelNumber;
for (int32_t channelNumber = 0; channelNumber < channels; channelNumber++) {
mappedChannelNumber = *(reinterpret_cast<uint8_t *>(opusHeadBuf) +
kOpusHeaderStreamMapOffset + channelNumber);
ASSERT_LT(mappedChannelNumber, channels) << "Invalid header generated. Channel mapping "
"cannot be greater than channel count.";
ASSERT_EQ(mappedChannelNumber, kOpusChannelMap[channels - 1][channelNumber])
<< "Invalid header generated. Channel mapping is not as per specification.";
}
} else {
ASSERT_LE(parsedHeader.channels, 2);
}
}
TEST_P(OpusHeaderParseTest, ParseTest) {
tuple<string, int32_t, bool, bool, bool, bool> params = GetParam();
string inputFileName = gEnv->getRes() + get<0>(params);
mEleStream.open(inputFileName, ifstream::binary);
ASSERT_EQ(mEleStream.is_open(), true) << "Failed to open inputfile " << get<0>(params);
bool isHeaderValid = get<2>(params);
bool isCodecDelayValid = get<3>(params);
bool isSeekPreRollValid = get<4>(params);
bool isInputValid = get<5>(params);
struct stat buf;
stat(inputFileName.c_str(), &buf);
size_t fileSize = buf.st_size;
mInputBuffer = (uint8_t *)malloc(fileSize);
ASSERT_NE(mInputBuffer, nullptr) << "Insufficient memory. Malloc failed for size " << fileSize;
mEleStream.read(reinterpret_cast<char *>(mInputBuffer), fileSize);
ASSERT_EQ(mEleStream.gcount(), fileSize) << "mEleStream.gcount() != bytesCount";
OpusHeader header;
size_t opusHeadSize = 0;
size_t codecDelayBufSize = 0;
size_t seekPreRollBufSize = 0;
void *opusHeadBuf = nullptr;
void *codecDelayBuf = nullptr;
void *seekPreRollBuf = nullptr;
bool status = GetOpusHeaderBuffers(mInputBuffer, fileSize, &opusHeadBuf, &opusHeadSize,
&codecDelayBuf, &codecDelayBufSize, &seekPreRollBuf,
&seekPreRollBufSize);
if (!isHeaderValid) {
ASSERT_EQ(opusHeadBuf, nullptr);
} else {
ASSERT_NE(opusHeadBuf, nullptr);
}
if (!isCodecDelayValid) {
ASSERT_EQ(codecDelayBuf, nullptr);
} else {
ASSERT_NE(codecDelayBuf, nullptr);
}
if (!isSeekPreRollValid) {
ASSERT_EQ(seekPreRollBuf, nullptr);
} else {
ASSERT_NE(seekPreRollBuf, nullptr);
}
if (!status) {
ASSERT_FALSE(isInputValid) << "GetOpusHeaderBuffers failed";
return;
}
status = ParseOpusHeader((uint8_t *)opusHeadBuf, opusHeadSize, &header);
if (status) {
ASSERT_TRUE(isInputValid) << "Parse opus header didn't fail for invalid input";
} else {
ASSERT_FALSE(isInputValid);
return;
}
int32_t channels = get<1>(params);
ASSERT_EQ(header.channels, channels) << "Parser returned invalid channel count";
ASSERT_LE(header.channels, kMaxChannels);
ASSERT_LE(header.num_coupled, header.num_streams)
<< "Invalid header generated. Number of coupled streams cannot be greater than number "
"of streams.";
ASSERT_EQ(header.num_coupled + header.num_streams, header.channels);
if (header.channel_mapping) {
uint8_t mappedChannelNumber;
for (int32_t channelNumber = 0; channelNumber < channels; channelNumber++) {
mappedChannelNumber = *(reinterpret_cast<uint8_t *>(opusHeadBuf) +
kOpusHeaderStreamMapOffset + channelNumber);
ASSERT_LT(mappedChannelNumber, channels)
<< "Invalid header. Channel mapping cannot be greater than channel count.";
ASSERT_EQ(mappedChannelNumber, kOpusChannelMap[channels - 1][channelNumber])
<< "Invalid header generated. Channel mapping "
"is not as per specification.";
}
}
}
INSTANTIATE_TEST_SUITE_P(OpusHeaderTestAll, OpusHeaderWriteTest,
::testing::Values(make_pair(1, 312),
make_pair(2, 312),
make_pair(5, 312),
make_pair(6, 312),
make_pair(1, 0),
make_pair(2, 0),
make_pair(5, 0),
make_pair(6, 0),
make_pair(1, 624),
make_pair(2, 624),
make_pair(5, 624),
make_pair(6, 624)));
INSTANTIATE_TEST_SUITE_P(
OpusHeaderTestAll, OpusHeaderParseTest,
::testing::Values(
make_tuple("2ch_valid_size83B.opus", 2, true, true, true, true),
make_tuple("3ch_valid_size88B.opus", 3, true, true, true, true),
make_tuple("5ch_valid.opus", 5, true, false, false, true),
make_tuple("6ch_valid.opus", 6, true, false, false, true),
make_tuple("1ch_valid.opus", 1, true, false, false, true),
make_tuple("2ch_valid.opus", 2, true, false, false, true),
make_tuple("3ch_invalid_size.opus", 3, true, true, true, false),
make_tuple("3ch_invalid_streams.opus", 3, true, true, true, false),
make_tuple("5ch_invalid_channelmapping.opus", 5, true, false, false, false),
make_tuple("5ch_invalid_coupledstreams.opus", 5, true, false, false, false),
make_tuple("6ch_invalid_channelmapping.opus", 6, true, false, false, false),
make_tuple("9ch_invalid_channels.opus", 9, true, true, true, false),
make_tuple("2ch_invalid_header.opus", 2, false, false, false, false),
make_tuple("2ch_invalid_headerlength_16.opus", 2, false, false, false, false),
make_tuple("2ch_invalid_headerlength_256.opus", 2, false, false, false, false),
make_tuple("2ch_invalid_size.opus", 2, false, false, false, false),
make_tuple("3ch_invalid_channelmapping_0.opus", 3, true, true, true, false),
make_tuple("3ch_invalid_coupledstreams.opus", 3, true, true, true, false),
make_tuple("3ch_invalid_headerlength.opus", 3, true, true, true, false),
make_tuple("3ch_invalid_headerSize1.opus", 3, false, false, false, false),
make_tuple("3ch_invalid_headerSize2.opus", 3, false, false, false, false),
make_tuple("3ch_invalid_headerSize3.opus", 3, false, false, false, false),
make_tuple("3ch_invalid_nodelay.opus", 3, false, false, false, false),
make_tuple("3ch_invalid_nopreroll.opus", 3, false, false, false, false)));
int main(int argc, char **argv) {
gEnv = new OpusHeaderTestEnvironment();
::testing::AddGlobalTestEnvironment(gEnv);
::testing::InitGoogleTest(&argc, argv);
int status = gEnv->initFromOptions(argc, argv);
if (status == 0) {
status = RUN_ALL_TESTS();
ALOGD("Opus Header Test Result = %d\n", status);
}
return status;
}