Merge "MediaTesting: Add Extractor Unit Test" am: dd8a4a8a77
am: a072553e09
am: 1228e2ad18
Change-Id: I65cc302357b483164cef1d3ad6e774d5745f322bgugelfrei
commit
fd6abf5778
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
cc_test {
|
||||||
|
name: "ExtractorUnitTest",
|
||||||
|
gtest: true,
|
||||||
|
|
||||||
|
srcs: ["ExtractorUnitTest.cpp"],
|
||||||
|
|
||||||
|
static_libs: [
|
||||||
|
"libaacextractor",
|
||||||
|
"libamrextractor",
|
||||||
|
"libmp3extractor",
|
||||||
|
"libwavextractor",
|
||||||
|
"liboggextractor",
|
||||||
|
"libflacextractor",
|
||||||
|
"libmidiextractor",
|
||||||
|
"libmkvextractor",
|
||||||
|
"libmpeg2extractor",
|
||||||
|
"libmp4extractor",
|
||||||
|
"libaudioutils",
|
||||||
|
"libdatasource",
|
||||||
|
|
||||||
|
"libstagefright",
|
||||||
|
"libstagefright_id3",
|
||||||
|
"libstagefright_flacdec",
|
||||||
|
"libstagefright_esds",
|
||||||
|
"libstagefright_mpeg2support",
|
||||||
|
"libstagefright_mpeg2extractor",
|
||||||
|
"libstagefright_foundation",
|
||||||
|
"libstagefright_metadatautils",
|
||||||
|
|
||||||
|
"libmedia_midiiowrapper",
|
||||||
|
"libsonivox",
|
||||||
|
"libvorbisidec",
|
||||||
|
"libwebm",
|
||||||
|
"libFLAC",
|
||||||
|
],
|
||||||
|
|
||||||
|
shared_libs: [
|
||||||
|
"android.hardware.cas@1.0",
|
||||||
|
"android.hardware.cas.native@1.0",
|
||||||
|
"android.hidl.token@1.0-utils",
|
||||||
|
"android.hidl.allocator@1.0",
|
||||||
|
"libbinder",
|
||||||
|
"libbinder_ndk",
|
||||||
|
"libutils",
|
||||||
|
"liblog",
|
||||||
|
"libcutils",
|
||||||
|
"libmediandk",
|
||||||
|
"libmedia",
|
||||||
|
"libcrypto",
|
||||||
|
"libhidlmemory",
|
||||||
|
"libhidlbase",
|
||||||
|
],
|
||||||
|
|
||||||
|
include_dirs: [
|
||||||
|
"frameworks/av/media/extractors/",
|
||||||
|
"frameworks/av/media/libstagefright/",
|
||||||
|
],
|
||||||
|
|
||||||
|
compile_multilib: "first",
|
||||||
|
|
||||||
|
cflags: [
|
||||||
|
"-Werror",
|
||||||
|
"-Wall",
|
||||||
|
],
|
||||||
|
|
||||||
|
ldflags: [
|
||||||
|
"-Wl",
|
||||||
|
"-Bsymbolic",
|
||||||
|
// to ignore duplicate symbol: GETEXTRACTORDEF
|
||||||
|
"-z muldefs",
|
||||||
|
],
|
||||||
|
|
||||||
|
sanitize: {
|
||||||
|
cfi: true,
|
||||||
|
misc_undefined: [
|
||||||
|
"unsigned-integer-overflow",
|
||||||
|
"signed-integer-overflow",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
@ -0,0 +1,528 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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 "ExtractorUnitTest"
|
||||||
|
#include <utils/Log.h>
|
||||||
|
|
||||||
|
#include <datasource/FileSource.h>
|
||||||
|
#include <media/stagefright/MediaBufferGroup.h>
|
||||||
|
#include <media/stagefright/MediaDefs.h>
|
||||||
|
#include <media/stagefright/MetaDataUtils.h>
|
||||||
|
|
||||||
|
#include "aac/AACExtractor.h"
|
||||||
|
#include "amr/AMRExtractor.h"
|
||||||
|
#include "flac/FLACExtractor.h"
|
||||||
|
#include "midi/MidiExtractor.h"
|
||||||
|
#include "mkv/MatroskaExtractor.h"
|
||||||
|
#include "mp3/MP3Extractor.h"
|
||||||
|
#include "mp4/MPEG4Extractor.h"
|
||||||
|
#include "mp4/SampleTable.h"
|
||||||
|
#include "mpeg2/MPEG2PSExtractor.h"
|
||||||
|
#include "mpeg2/MPEG2TSExtractor.h"
|
||||||
|
#include "ogg/OggExtractor.h"
|
||||||
|
#include "wav/WAVExtractor.h"
|
||||||
|
|
||||||
|
#include "ExtractorUnitTestEnvironment.h"
|
||||||
|
|
||||||
|
using namespace android;
|
||||||
|
|
||||||
|
#define OUTPUT_DUMP_FILE "/data/local/tmp/extractorOutput"
|
||||||
|
|
||||||
|
constexpr int32_t kMaxCount = 10;
|
||||||
|
constexpr int32_t kOpusSeekPreRollUs = 80000; // 80 ms;
|
||||||
|
|
||||||
|
static ExtractorUnitTestEnvironment *gEnv = nullptr;
|
||||||
|
|
||||||
|
class ExtractorUnitTest : public ::testing::TestWithParam<pair<string, string>> {
|
||||||
|
public:
|
||||||
|
ExtractorUnitTest() : mInputFp(nullptr), mDataSource(nullptr), mExtractor(nullptr) {}
|
||||||
|
|
||||||
|
~ExtractorUnitTest() {
|
||||||
|
if (mInputFp) {
|
||||||
|
fclose(mInputFp);
|
||||||
|
mInputFp = nullptr;
|
||||||
|
}
|
||||||
|
if (mDataSource) {
|
||||||
|
mDataSource.clear();
|
||||||
|
mDataSource = nullptr;
|
||||||
|
}
|
||||||
|
if (mExtractor) {
|
||||||
|
delete mExtractor;
|
||||||
|
mExtractor = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void SetUp() override {
|
||||||
|
mExtractorName = unknown_comp;
|
||||||
|
mDisableTest = false;
|
||||||
|
|
||||||
|
static const std::map<std::string, standardExtractors> mapExtractor = {
|
||||||
|
{"aac", AAC}, {"amr", AMR}, {"mp3", MP3}, {"ogg", OGG},
|
||||||
|
{"wav", WAV}, {"mkv", MKV}, {"flac", FLAC}, {"midi", MIDI},
|
||||||
|
{"mpeg4", MPEG4}, {"mpeg2ts", MPEG2TS}, {"mpeg2ps", MPEG2PS}};
|
||||||
|
// Find the component type
|
||||||
|
string writerFormat = GetParam().first;
|
||||||
|
if (mapExtractor.find(writerFormat) != mapExtractor.end()) {
|
||||||
|
mExtractorName = mapExtractor.at(writerFormat);
|
||||||
|
}
|
||||||
|
if (mExtractorName == standardExtractors::unknown_comp) {
|
||||||
|
cout << "[ WARN ] Test Skipped. Invalid extractor\n";
|
||||||
|
mDisableTest = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t setDataSource(string inputFileName);
|
||||||
|
|
||||||
|
int32_t createExtractor();
|
||||||
|
|
||||||
|
enum standardExtractors {
|
||||||
|
AAC,
|
||||||
|
AMR,
|
||||||
|
FLAC,
|
||||||
|
MIDI,
|
||||||
|
MKV,
|
||||||
|
MP3,
|
||||||
|
MPEG4,
|
||||||
|
MPEG2PS,
|
||||||
|
MPEG2TS,
|
||||||
|
OGG,
|
||||||
|
WAV,
|
||||||
|
unknown_comp,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool mDisableTest;
|
||||||
|
standardExtractors mExtractorName;
|
||||||
|
|
||||||
|
FILE *mInputFp;
|
||||||
|
sp<DataSource> mDataSource;
|
||||||
|
MediaExtractorPluginHelper *mExtractor;
|
||||||
|
};
|
||||||
|
|
||||||
|
int32_t ExtractorUnitTest::setDataSource(string inputFileName) {
|
||||||
|
mInputFp = fopen(inputFileName.c_str(), "rb");
|
||||||
|
if (!mInputFp) {
|
||||||
|
ALOGE("Unable to open input file for reading");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
struct stat buf;
|
||||||
|
stat(inputFileName.c_str(), &buf);
|
||||||
|
int32_t fd = fileno(mInputFp);
|
||||||
|
mDataSource = new FileSource(dup(fd), 0, buf.st_size);
|
||||||
|
if (!mDataSource) return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t ExtractorUnitTest::createExtractor() {
|
||||||
|
switch (mExtractorName) {
|
||||||
|
case AAC:
|
||||||
|
mExtractor = new AACExtractor(new DataSourceHelper(mDataSource->wrap()), 0);
|
||||||
|
break;
|
||||||
|
case AMR:
|
||||||
|
mExtractor = new AMRExtractor(new DataSourceHelper(mDataSource->wrap()));
|
||||||
|
break;
|
||||||
|
case MP3:
|
||||||
|
mExtractor = new MP3Extractor(new DataSourceHelper(mDataSource->wrap()), nullptr);
|
||||||
|
break;
|
||||||
|
case OGG:
|
||||||
|
mExtractor = new OggExtractor(new DataSourceHelper(mDataSource->wrap()));
|
||||||
|
break;
|
||||||
|
case WAV:
|
||||||
|
mExtractor = new WAVExtractor(new DataSourceHelper(mDataSource->wrap()));
|
||||||
|
break;
|
||||||
|
case MKV:
|
||||||
|
mExtractor = new MatroskaExtractor(new DataSourceHelper(mDataSource->wrap()));
|
||||||
|
break;
|
||||||
|
case FLAC:
|
||||||
|
mExtractor = new FLACExtractor(new DataSourceHelper(mDataSource->wrap()));
|
||||||
|
break;
|
||||||
|
case MPEG4:
|
||||||
|
mExtractor = new MPEG4Extractor(new DataSourceHelper(mDataSource->wrap()));
|
||||||
|
break;
|
||||||
|
case MPEG2TS:
|
||||||
|
mExtractor = new MPEG2TSExtractor(new DataSourceHelper(mDataSource->wrap()));
|
||||||
|
break;
|
||||||
|
case MPEG2PS:
|
||||||
|
mExtractor = new MPEG2PSExtractor(new DataSourceHelper(mDataSource->wrap()));
|
||||||
|
break;
|
||||||
|
case MIDI:
|
||||||
|
mExtractor = new MidiExtractor(mDataSource->wrap());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!mExtractor) return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void getSeekablePoints(vector<int64_t> &seekablePoints, MediaTrackHelper *track) {
|
||||||
|
int32_t status = 0;
|
||||||
|
if (!seekablePoints.empty()) {
|
||||||
|
seekablePoints.clear();
|
||||||
|
}
|
||||||
|
int64_t timeStamp;
|
||||||
|
while (status != AMEDIA_ERROR_END_OF_STREAM) {
|
||||||
|
MediaBufferHelper *buffer = nullptr;
|
||||||
|
status = track->read(&buffer);
|
||||||
|
if (buffer) {
|
||||||
|
AMediaFormat *metaData = buffer->meta_data();
|
||||||
|
int32_t isSync = 0;
|
||||||
|
AMediaFormat_getInt32(metaData, AMEDIAFORMAT_KEY_IS_SYNC_FRAME, &isSync);
|
||||||
|
if (isSync) {
|
||||||
|
AMediaFormat_getInt64(metaData, AMEDIAFORMAT_KEY_TIME_US, &timeStamp);
|
||||||
|
seekablePoints.push_back(timeStamp);
|
||||||
|
}
|
||||||
|
buffer->release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(ExtractorUnitTest, CreateExtractorTest) {
|
||||||
|
if (mDisableTest) return;
|
||||||
|
|
||||||
|
ALOGV("Checks if a valid extractor is created for a given input file");
|
||||||
|
string inputFileName = gEnv->getRes() + GetParam().second;
|
||||||
|
|
||||||
|
ASSERT_EQ(setDataSource(inputFileName), 0)
|
||||||
|
<< "SetDataSource failed for" << GetParam().first << "extractor";
|
||||||
|
|
||||||
|
ASSERT_EQ(createExtractor(), 0)
|
||||||
|
<< "Extractor creation failed for" << GetParam().first << "extractor";
|
||||||
|
|
||||||
|
// A valid extractor instace should return success for following calls
|
||||||
|
ASSERT_GT(mExtractor->countTracks(), 0);
|
||||||
|
|
||||||
|
AMediaFormat *format = AMediaFormat_new();
|
||||||
|
ASSERT_NE(format, nullptr) << "AMediaFormat_new returned null AMediaformat";
|
||||||
|
|
||||||
|
ASSERT_EQ(mExtractor->getMetaData(format), AMEDIA_OK);
|
||||||
|
AMediaFormat_delete(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(ExtractorUnitTest, ExtractorTest) {
|
||||||
|
if (mDisableTest) return;
|
||||||
|
|
||||||
|
ALOGV("Validates %s Extractor for a given input file", GetParam().first.c_str());
|
||||||
|
string inputFileName = gEnv->getRes() + GetParam().second;
|
||||||
|
|
||||||
|
int32_t status = setDataSource(inputFileName);
|
||||||
|
ASSERT_EQ(status, 0) << "SetDataSource failed for" << GetParam().first << "extractor";
|
||||||
|
|
||||||
|
status = createExtractor();
|
||||||
|
ASSERT_EQ(status, 0) << "Extractor creation failed for" << GetParam().first << "extractor";
|
||||||
|
|
||||||
|
int32_t numTracks = mExtractor->countTracks();
|
||||||
|
ASSERT_GT(numTracks, 0) << "Extractor didn't find any track for the given clip";
|
||||||
|
|
||||||
|
for (int32_t idx = 0; idx < numTracks; idx++) {
|
||||||
|
MediaTrackHelper *track = mExtractor->getTrack(idx);
|
||||||
|
ASSERT_NE(track, nullptr) << "Failed to get track for index " << idx;
|
||||||
|
|
||||||
|
CMediaTrack *cTrack = wrap(track);
|
||||||
|
ASSERT_NE(cTrack, nullptr) << "Failed to get track wrapper for index " << idx;
|
||||||
|
|
||||||
|
MediaBufferGroup *bufferGroup = new MediaBufferGroup();
|
||||||
|
status = cTrack->start(track, bufferGroup->wrap());
|
||||||
|
ASSERT_EQ(OK, (media_status_t)status) << "Failed to start the track";
|
||||||
|
|
||||||
|
FILE *outFp = fopen((OUTPUT_DUMP_FILE + to_string(idx)).c_str(), "wb");
|
||||||
|
if (!outFp) {
|
||||||
|
ALOGW("Unable to open output file for dumping extracted stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (status != AMEDIA_ERROR_END_OF_STREAM) {
|
||||||
|
MediaBufferHelper *buffer = nullptr;
|
||||||
|
status = track->read(&buffer);
|
||||||
|
ALOGV("track->read Status = %d buffer %p", status, buffer);
|
||||||
|
if (buffer) {
|
||||||
|
ALOGV("buffer->data %p buffer->size() %zu buffer->range_length() %zu",
|
||||||
|
buffer->data(), buffer->size(), buffer->range_length());
|
||||||
|
if (outFp) fwrite(buffer->data(), 1, buffer->range_length(), outFp);
|
||||||
|
buffer->release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (outFp) fclose(outFp);
|
||||||
|
status = cTrack->stop(track);
|
||||||
|
ASSERT_EQ(OK, status) << "Failed to stop the track";
|
||||||
|
delete bufferGroup;
|
||||||
|
delete track;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(ExtractorUnitTest, MetaDataComparisonTest) {
|
||||||
|
if (mDisableTest) return;
|
||||||
|
|
||||||
|
ALOGV("Validates Extractor's meta data for a given input file");
|
||||||
|
string inputFileName = gEnv->getRes() + GetParam().second;
|
||||||
|
|
||||||
|
int32_t status = setDataSource(inputFileName);
|
||||||
|
ASSERT_EQ(status, 0) << "SetDataSource failed for" << GetParam().first << "extractor";
|
||||||
|
|
||||||
|
status = createExtractor();
|
||||||
|
ASSERT_EQ(status, 0) << "Extractor creation failed for" << GetParam().first << "extractor";
|
||||||
|
|
||||||
|
int32_t numTracks = mExtractor->countTracks();
|
||||||
|
ASSERT_GT(numTracks, 0) << "Extractor didn't find any track for the given clip";
|
||||||
|
|
||||||
|
AMediaFormat *extractorFormat = AMediaFormat_new();
|
||||||
|
ASSERT_NE(extractorFormat, nullptr) << "AMediaFormat_new returned null AMediaformat";
|
||||||
|
AMediaFormat *trackFormat = AMediaFormat_new();
|
||||||
|
ASSERT_NE(trackFormat, nullptr) << "AMediaFormat_new returned null AMediaformat";
|
||||||
|
|
||||||
|
for (int32_t idx = 0; idx < numTracks; idx++) {
|
||||||
|
MediaTrackHelper *track = mExtractor->getTrack(idx);
|
||||||
|
ASSERT_NE(track, nullptr) << "Failed to get track for index " << idx;
|
||||||
|
|
||||||
|
CMediaTrack *cTrack = wrap(track);
|
||||||
|
ASSERT_NE(cTrack, nullptr) << "Failed to get track wrapper for index " << idx;
|
||||||
|
|
||||||
|
MediaBufferGroup *bufferGroup = new MediaBufferGroup();
|
||||||
|
status = cTrack->start(track, bufferGroup->wrap());
|
||||||
|
ASSERT_EQ(OK, (media_status_t)status) << "Failed to start the track";
|
||||||
|
|
||||||
|
status = mExtractor->getTrackMetaData(extractorFormat, idx, 1);
|
||||||
|
ASSERT_EQ(OK, (media_status_t)status) << "Failed to get trackMetaData";
|
||||||
|
|
||||||
|
status = track->getFormat(trackFormat);
|
||||||
|
ASSERT_EQ(OK, (media_status_t)status) << "Failed to get track meta data";
|
||||||
|
|
||||||
|
const char *extractorMime, *trackMime;
|
||||||
|
AMediaFormat_getString(extractorFormat, AMEDIAFORMAT_KEY_MIME, &extractorMime);
|
||||||
|
AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &trackMime);
|
||||||
|
ASSERT_TRUE(!strcmp(extractorMime, trackMime))
|
||||||
|
<< "Extractor's format doesn't match track format";
|
||||||
|
|
||||||
|
if (!strncmp(extractorMime, "audio/", 6)) {
|
||||||
|
int32_t exSampleRate, exChannelCount;
|
||||||
|
int32_t trackSampleRate, trackChannelCount;
|
||||||
|
ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat, AMEDIAFORMAT_KEY_CHANNEL_COUNT,
|
||||||
|
&exChannelCount));
|
||||||
|
ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat, AMEDIAFORMAT_KEY_SAMPLE_RATE,
|
||||||
|
&exSampleRate));
|
||||||
|
ASSERT_TRUE(AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_CHANNEL_COUNT,
|
||||||
|
&trackChannelCount));
|
||||||
|
ASSERT_TRUE(AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_SAMPLE_RATE,
|
||||||
|
&trackSampleRate));
|
||||||
|
ASSERT_EQ(exChannelCount, trackChannelCount) << "ChannelCount not as expected";
|
||||||
|
ASSERT_EQ(exSampleRate, trackSampleRate) << "SampleRate not as expected";
|
||||||
|
} else {
|
||||||
|
int32_t exWidth, exHeight;
|
||||||
|
int32_t trackWidth, trackHeight;
|
||||||
|
ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat, AMEDIAFORMAT_KEY_WIDTH, &exWidth));
|
||||||
|
ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat, AMEDIAFORMAT_KEY_HEIGHT, &exHeight));
|
||||||
|
ASSERT_TRUE(AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_WIDTH, &trackWidth));
|
||||||
|
ASSERT_TRUE(AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_HEIGHT, &trackHeight));
|
||||||
|
ASSERT_EQ(exWidth, trackWidth) << "Width not as expected";
|
||||||
|
ASSERT_EQ(exHeight, trackHeight) << "Height not as expected";
|
||||||
|
}
|
||||||
|
status = cTrack->stop(track);
|
||||||
|
ASSERT_EQ(OK, status) << "Failed to stop the track";
|
||||||
|
delete bufferGroup;
|
||||||
|
delete track;
|
||||||
|
}
|
||||||
|
AMediaFormat_delete(trackFormat);
|
||||||
|
AMediaFormat_delete(extractorFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(ExtractorUnitTest, MultipleStartStopTest) {
|
||||||
|
if (mDisableTest) return;
|
||||||
|
|
||||||
|
ALOGV("Test %s extractor for multiple start and stop calls", GetParam().first.c_str());
|
||||||
|
string inputFileName = gEnv->getRes() + GetParam().second;
|
||||||
|
|
||||||
|
int32_t status = setDataSource(inputFileName);
|
||||||
|
ASSERT_EQ(status, 0) << "SetDataSource failed for" << GetParam().first << "extractor";
|
||||||
|
|
||||||
|
status = createExtractor();
|
||||||
|
ASSERT_EQ(status, 0) << "Extractor creation failed for" << GetParam().first << "extractor";
|
||||||
|
|
||||||
|
int32_t numTracks = mExtractor->countTracks();
|
||||||
|
ASSERT_GT(numTracks, 0) << "Extractor didn't find any track for the given clip";
|
||||||
|
|
||||||
|
// start/stop the tracks multiple times
|
||||||
|
for (int32_t count = 0; count < kMaxCount; count++) {
|
||||||
|
for (int32_t idx = 0; idx < numTracks; idx++) {
|
||||||
|
MediaTrackHelper *track = mExtractor->getTrack(idx);
|
||||||
|
ASSERT_NE(track, nullptr) << "Failed to get track for index " << idx;
|
||||||
|
|
||||||
|
CMediaTrack *cTrack = wrap(track);
|
||||||
|
ASSERT_NE(cTrack, nullptr) << "Failed to get track wrapper for index " << idx;
|
||||||
|
|
||||||
|
MediaBufferGroup *bufferGroup = new MediaBufferGroup();
|
||||||
|
status = cTrack->start(track, bufferGroup->wrap());
|
||||||
|
ASSERT_EQ(OK, (media_status_t)status) << "Failed to start the track";
|
||||||
|
MediaBufferHelper *buffer = nullptr;
|
||||||
|
status = track->read(&buffer);
|
||||||
|
if (buffer) {
|
||||||
|
ALOGV("buffer->data %p buffer->size() %zu buffer->range_length() %zu",
|
||||||
|
buffer->data(), buffer->size(), buffer->range_length());
|
||||||
|
buffer->release();
|
||||||
|
}
|
||||||
|
status = cTrack->stop(track);
|
||||||
|
ASSERT_EQ(OK, status) << "Failed to stop the track";
|
||||||
|
delete bufferGroup;
|
||||||
|
delete track;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(ExtractorUnitTest, SeekTest) {
|
||||||
|
// Both Flac and Wav extractor can give samples from any pts and mark the given sample as
|
||||||
|
// sync frame. So, this seek test is not applicable to FLAC and WAV extractors
|
||||||
|
if (mDisableTest || mExtractorName == FLAC || mExtractorName == WAV) return;
|
||||||
|
|
||||||
|
ALOGV("Validates %s Extractor behaviour for different seek modes", GetParam().first.c_str());
|
||||||
|
string inputFileName = gEnv->getRes() + GetParam().second;
|
||||||
|
|
||||||
|
int32_t status = setDataSource(inputFileName);
|
||||||
|
ASSERT_EQ(status, 0) << "SetDataSource failed for" << GetParam().first << "extractor";
|
||||||
|
|
||||||
|
status = createExtractor();
|
||||||
|
ASSERT_EQ(status, 0) << "Extractor creation failed for" << GetParam().first << "extractor";
|
||||||
|
|
||||||
|
int32_t numTracks = mExtractor->countTracks();
|
||||||
|
ASSERT_GT(numTracks, 0) << "Extractor didn't find any track for the given clip";
|
||||||
|
|
||||||
|
uint32_t seekFlag = mExtractor->flags();
|
||||||
|
if (!(seekFlag & MediaExtractorPluginHelper::CAN_SEEK)) {
|
||||||
|
cout << "[ WARN ] Test Skipped. " << GetParam().first
|
||||||
|
<< " Extractor doesn't support seek\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<int64_t> seekablePoints;
|
||||||
|
for (int32_t idx = 0; idx < numTracks; idx++) {
|
||||||
|
MediaTrackHelper *track = mExtractor->getTrack(idx);
|
||||||
|
ASSERT_NE(track, nullptr) << "Failed to get track for index " << idx;
|
||||||
|
|
||||||
|
CMediaTrack *cTrack = wrap(track);
|
||||||
|
ASSERT_NE(cTrack, nullptr) << "Failed to get track wrapper for index " << idx;
|
||||||
|
|
||||||
|
// Get all the seekable points of a given input
|
||||||
|
MediaBufferGroup *bufferGroup = new MediaBufferGroup();
|
||||||
|
status = cTrack->start(track, bufferGroup->wrap());
|
||||||
|
ASSERT_EQ(OK, (media_status_t)status) << "Failed to start the track";
|
||||||
|
getSeekablePoints(seekablePoints, track);
|
||||||
|
ASSERT_GT(seekablePoints.size(), 0)
|
||||||
|
<< "Failed to get seekable points for " << GetParam().first << " extractor";
|
||||||
|
|
||||||
|
AMediaFormat *trackFormat = AMediaFormat_new();
|
||||||
|
ASSERT_NE(trackFormat, nullptr) << "AMediaFormat_new returned null format";
|
||||||
|
status = track->getFormat(trackFormat);
|
||||||
|
ASSERT_EQ(OK, (media_status_t)status) << "Failed to get track meta data";
|
||||||
|
|
||||||
|
bool isOpus = false;
|
||||||
|
const char *mime;
|
||||||
|
AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime);
|
||||||
|
if (!strcmp(mime, "audio/opus")) isOpus = true;
|
||||||
|
AMediaFormat_delete(trackFormat);
|
||||||
|
|
||||||
|
int32_t seekIdx = 0;
|
||||||
|
size_t seekablePointsSize = seekablePoints.size();
|
||||||
|
for (int32_t mode = CMediaTrackReadOptions::SEEK_PREVIOUS_SYNC;
|
||||||
|
mode <= CMediaTrackReadOptions::SEEK_CLOSEST; mode++) {
|
||||||
|
for (int32_t seekCount = 0; seekCount < kMaxCount; seekCount++) {
|
||||||
|
seekIdx = rand() % seekablePointsSize + 1;
|
||||||
|
if (seekIdx >= seekablePointsSize) seekIdx = seekablePointsSize - 1;
|
||||||
|
|
||||||
|
int64_t seekToTimeStamp = seekablePoints[seekIdx];
|
||||||
|
if (seekablePointsSize > 1) {
|
||||||
|
int64_t prevTimeStamp = seekablePoints[seekIdx - 1];
|
||||||
|
seekToTimeStamp = seekToTimeStamp - ((seekToTimeStamp - prevTimeStamp) >> 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opus has a seekPreRollUs. TimeStamp returned by the
|
||||||
|
// extractor is calculated based on (seekPts - seekPreRollUs).
|
||||||
|
// So we add the preRoll value to the timeStamp we want to seek to.
|
||||||
|
if (isOpus) {
|
||||||
|
seekToTimeStamp += kOpusSeekPreRollUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaTrackHelper::ReadOptions *options = new MediaTrackHelper::ReadOptions(
|
||||||
|
mode | CMediaTrackReadOptions::SEEK, seekToTimeStamp);
|
||||||
|
ASSERT_NE(options, nullptr) << "Cannot create read option";
|
||||||
|
|
||||||
|
MediaBufferHelper *buffer = nullptr;
|
||||||
|
status = track->read(&buffer, options);
|
||||||
|
if (status == AMEDIA_ERROR_END_OF_STREAM) {
|
||||||
|
delete options;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (buffer) {
|
||||||
|
AMediaFormat *metaData = buffer->meta_data();
|
||||||
|
int64_t timeStamp;
|
||||||
|
AMediaFormat_getInt64(metaData, AMEDIAFORMAT_KEY_TIME_US, &timeStamp);
|
||||||
|
buffer->release();
|
||||||
|
|
||||||
|
// CMediaTrackReadOptions::SEEK is 8. Using mask 0111b to get true modes
|
||||||
|
switch (mode & 0x7) {
|
||||||
|
case CMediaTrackReadOptions::SEEK_PREVIOUS_SYNC:
|
||||||
|
if (seekablePointsSize == 1) {
|
||||||
|
EXPECT_EQ(timeStamp, seekablePoints[seekIdx]);
|
||||||
|
} else {
|
||||||
|
EXPECT_EQ(timeStamp, seekablePoints[seekIdx - 1]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CMediaTrackReadOptions::SEEK_NEXT_SYNC:
|
||||||
|
case CMediaTrackReadOptions::SEEK_CLOSEST_SYNC:
|
||||||
|
case CMediaTrackReadOptions::SEEK_CLOSEST:
|
||||||
|
EXPECT_EQ(timeStamp, seekablePoints[seekIdx]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
status = cTrack->stop(track);
|
||||||
|
ASSERT_EQ(OK, status) << "Failed to stop the track";
|
||||||
|
delete bufferGroup;
|
||||||
|
delete track;
|
||||||
|
}
|
||||||
|
seekablePoints.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: (b/145332185)
|
||||||
|
// Add MIDI inputs
|
||||||
|
INSTANTIATE_TEST_SUITE_P(ExtractorUnitTestAll, ExtractorUnitTest,
|
||||||
|
::testing::Values(make_pair("aac", "loudsoftaac.aac"),
|
||||||
|
make_pair("amr", "testamr.amr"),
|
||||||
|
make_pair("amr", "amrwb.wav"),
|
||||||
|
make_pair("ogg", "john_cage.ogg"),
|
||||||
|
make_pair("wav", "monotestgsm.wav"),
|
||||||
|
make_pair("mpeg2ts", "segment000001.ts"),
|
||||||
|
make_pair("flac", "sinesweepflac.flac"),
|
||||||
|
make_pair("ogg", "testopus.opus"),
|
||||||
|
make_pair("mkv", "sinesweepvorbis.mkv"),
|
||||||
|
make_pair("mpeg4", "sinesweepoggmp4.mp4"),
|
||||||
|
make_pair("mp3", "sinesweepmp3lame.mp3"),
|
||||||
|
make_pair("mkv", "swirl_144x136_vp9.webm"),
|
||||||
|
make_pair("mkv", "swirl_144x136_vp8.webm"),
|
||||||
|
make_pair("mpeg2ps", "swirl_144x136_mpeg2.mpg"),
|
||||||
|
make_pair("mpeg4", "swirl_132x130_mpeg4.mp4")));
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
gEnv = new ExtractorUnitTestEnvironment();
|
||||||
|
::testing::AddGlobalTestEnvironment(gEnv);
|
||||||
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
|
int status = gEnv->initFromOptions(argc, argv);
|
||||||
|
if (status == 0) {
|
||||||
|
status = RUN_ALL_TESTS();
|
||||||
|
ALOGV("Test result = %d\n", status);
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __EXTRACTOR_UNIT_TEST_ENVIRONMENT_H__
|
||||||
|
#define __EXTRACTOR_UNIT_TEST_ENVIRONMENT_H__
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <getopt.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
class ExtractorUnitTestEnvironment : public ::testing::Environment {
|
||||||
|
public:
|
||||||
|
ExtractorUnitTestEnvironment() : res("/data/local/tmp/") {}
|
||||||
|
|
||||||
|
// Parses the command line arguments
|
||||||
|
int initFromOptions(int argc, char **argv);
|
||||||
|
|
||||||
|
void setRes(const char *_res) { res = _res; }
|
||||||
|
|
||||||
|
const string getRes() const { return res; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
string res;
|
||||||
|
};
|
||||||
|
|
||||||
|
int ExtractorUnitTestEnvironment::initFromOptions(int argc, char **argv) {
|
||||||
|
static struct option options[] = {{"res", required_argument, 0, 'P'}, {0, 0, 0, 0}};
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
int index = 0;
|
||||||
|
int c = getopt_long(argc, argv, "P:", options, &index);
|
||||||
|
if (c == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case 'P':
|
||||||
|
setRes(optarg);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optind < argc) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"unrecognized option: %s\n\n"
|
||||||
|
"usage: %s <gtest options> <test options>\n\n"
|
||||||
|
"test options are:\n\n"
|
||||||
|
"-P, --path: Resource files directory location\n",
|
||||||
|
argv[optind ?: 1], argv[0]);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __EXTRACTOR_UNIT_TEST_ENVIRONMENT_H__
|
@ -0,0 +1,34 @@
|
|||||||
|
## Media Testing ##
|
||||||
|
---
|
||||||
|
#### Extractor :
|
||||||
|
The Extractor Test Suite validates the extractors available in the device.
|
||||||
|
|
||||||
|
Run the following steps to build the test suite:
|
||||||
|
```
|
||||||
|
m ExtractorUnitTest
|
||||||
|
```
|
||||||
|
|
||||||
|
The 32-bit binaries will be created in the following path : ${OUT}/data/nativetest/
|
||||||
|
|
||||||
|
The 64-bit binaries will be created in the following path : ${OUT}/data/nativetest64/
|
||||||
|
|
||||||
|
To test 64-bit binary push binaries from nativetest64.
|
||||||
|
```
|
||||||
|
adb push ${OUT}/data/nativetest64/ExtractorUnitTest/ExtractorUnitTest /data/local/tmp/
|
||||||
|
```
|
||||||
|
|
||||||
|
To test 32-bit binary push binaries from nativetest.
|
||||||
|
```
|
||||||
|
adb push ${OUT}/data/nativetest/ExtractorUnitTest/ExtractorUnitTest /data/local/tmp/
|
||||||
|
```
|
||||||
|
|
||||||
|
The resource file for the tests is taken from [here](https://drive.google.com/drive/folders/1Z9nCIRB6pGLvb5mPkF8BURa5Nc6cY9pY). Push these files into device for testing.
|
||||||
|
Download extractor folder and push all the files in this folder to /data/local/tmp/ on the device.
|
||||||
|
```
|
||||||
|
adb push extractor /data/local/tmp/
|
||||||
|
```
|
||||||
|
|
||||||
|
usage: ExtractorUnitTest -P \<path_to_folder\>
|
||||||
|
```
|
||||||
|
adb shell /data/local/tmp/ExtractorUnitTest -P /data/local/tmp/extractor/
|
||||||
|
```
|
Loading…
Reference in new issue