diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2020-11-10 23:22:49 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-11-10 23:22:49 +0000 |
commit | 542b8104e59a57439a70b7cc456ffabe6226a927 (patch) | |
tree | d0d38ebabbba4d9ae5c8c67a77b4bf1b041e4480 | |
parent | d46c5d6226eead887ed7ceb9c7ee511c0725d481 (diff) | |
parent | e6c4e8739d899776f39d3667676e12162522209a (diff) | |
download | tremolo-542b8104e59a57439a70b7cc456ffabe6226a927.tar.gz |
Merge "MediaTesting: Add Vorbis Decoder Unit Test" am: 18ac1d4e55 am: e6c4e8739d
Original change: https://android-review.googlesource.com/c/platform/external/tremolo/+/1487218
Change-Id: Ic912e8b56993d22357a0dc65bb482dfd6fd4456c
-rw-r--r-- | tests/Android.bp | 31 | ||||
-rw-r--r-- | tests/AndroidTest.xml | 28 | ||||
-rw-r--r-- | tests/README.md | 39 | ||||
-rw-r--r-- | tests/VorbisDecoderTest.cpp | 343 | ||||
-rw-r--r-- | tests/VorbisDecoderTestEnvironment.h | 83 |
5 files changed, 524 insertions, 0 deletions
diff --git a/tests/Android.bp b/tests/Android.bp new file mode 100644 index 0000000..ea3dd9d --- /dev/null +++ b/tests/Android.bp @@ -0,0 +1,31 @@ +cc_test { + name: "VorbisDecoderTest", + gtest: true, + test_suites: ["device-tests"], + + srcs: [ + "VorbisDecoderTest.cpp", + ], + + shared_libs: [ + "libutils", + "liblog", + ], + + static_libs: [ + "libvorbisidec", + ], + + cflags: [ + "-Werror", + "-Wall", + ], + + sanitize: { + misc_undefined: [ + "unsigned-integer-overflow", + "signed-integer-overflow", + ], + cfi: true, + }, +} diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml new file mode 100644 index 0000000..cb81d5f --- /dev/null +++ b/tests/AndroidTest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Test module config for vorbis decoder unit test"> + <option name="test-suite-tag" value="VorbisDecoderTest" /> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" value="VorbisDecoderTest->/data/local/tmp/VorbisDecoderTest" /> + <option name="push-file" + key="https://storage.googleapis.com/android_media/external/tremolo/tests/VorbisDecoderRes-1.0.zip?unzip=true" + value="/data/local/tmp/VorbisDecoderTestRes/" /> + </target_preparer> + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="VorbisDecoderTest" /> + <option name="native-test-flag" value="-P /data/local/tmp/VorbisDecoderTestRes/" /> + <option name="native-test-flag" value="-C true" /> + </test> +</configuration> diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..8eb17cd --- /dev/null +++ b/tests/README.md @@ -0,0 +1,39 @@ +## Media Testing ## +--- +#### Vorbis Decoder +The VorbisDecoder Test Suite validates the libvorbisidec library available in external/tremelo. + +Run the following steps to build the test suite: +``` +m VorbisDecoderTest +``` + +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/VorbisDecoderTest/VorbisDecoderTest /data/local/tmp/ +``` + +To test 32-bit binary push binaries from nativetest. +``` +adb push ${OUT}/data/nativetest/VorbisDecoderTest/VorbisDecoderTest /data/local/tmp/ +``` + +The resource file for the tests is taken from [here](https://storage.googleapis.com/android_media/external/tremolo/tests/VorbisDecoderRes-1.0.zip). Download, unzip and push these files into device for testing. + +``` +adb push VorbisDecoderTestRes /data/local/tmp/ +``` + +usage: VorbisDecoderTest -P \<path_to_folder\> -C <remove_output_file> +``` +adb shell /data/local/tmp/VorbisDecoderTest -P /data/local/tmp/VorbisDecoderTestRes/ -C true +``` +Alternatively, the test can also be run using atest command. + +``` +atest VorbisDecoderTest -- --enable-module-dynamic-download=true +``` diff --git a/tests/VorbisDecoderTest.cpp b/tests/VorbisDecoderTest.cpp new file mode 100644 index 0000000..22e19a6 --- /dev/null +++ b/tests/VorbisDecoderTest.cpp @@ -0,0 +1,343 @@ +/* + * 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 "VorbisDecoderTest" +#include <utils/Log.h> + +#include <fstream> + +#include "VorbisDecoderTestEnvironment.h" + +#define OUTPUT_FILE_NAME "/data/local/tmp/VorbisDecoderOutput.raw" + +constexpr uint32_t kMaxChannels = 255; +constexpr uint32_t kMaxNumSamplesPerChannel = 8192; + +struct vorbis_dsp_state; +struct vorbis_info; +struct FrameInfo { + int32_t bytesCount; + uint32_t flags; + int64_t timestamp; +}; + +extern "C" { + #include <Tremolo/codec_internal.h> + + int _vorbis_unpack_books(vorbis_info* vi, oggpack_buffer* opb); + int _vorbis_unpack_info(vorbis_info* vi, oggpack_buffer* opb); + int _vorbis_unpack_comment(vorbis_comment* vc, oggpack_buffer* opb); +} + +static VorbisDecoderTestEnvironment* gEnv = nullptr; + +class VorbisDecoderTest : public ::testing::TestWithParam<pair<string, string>> { + public: + VorbisDecoderTest() + : mNumFramesLeftOnPage(-1), + mInfoUnpacked(false), + mBooksUnpacked(false), + mInputBuffer(nullptr), + mOutputBuffer(nullptr), + mState(nullptr), + mVi(nullptr) {} + + ~VorbisDecoderTest() { + if (mInputBuffer) free(mInputBuffer); + if (mOutputBuffer) free(mOutputBuffer); + if (mEleStream.is_open()) mEleStream.close(); + if (mState) { + vorbis_dsp_clear(mState); + delete mState; + mState = nullptr; + } + + if (mVi) { + vorbis_info_clear(mVi); + delete mVi; + mVi = nullptr; + } + mNumFramesLeftOnPage = -1; + if (gEnv->cleanUp()) remove(OUTPUT_FILE_NAME); + } + + int32_t initVorbisDecoder(); + void processVorbisDecoder(vector<FrameInfo> Info, int32_t offset, int32_t range, + ofstream& ostrm); + + ifstream mEleStream; + int32_t mNumFramesLeftOnPage; + bool mInfoUnpacked; + bool mBooksUnpacked; + char* mInputBuffer; + int16_t* mOutputBuffer; + vorbis_dsp_state* mState; + vorbis_info* mVi; +}; + +void getInfo(string infoFileName, vector<FrameInfo>& Info) { + ifstream eleInfo; + eleInfo.open(infoFileName); + ASSERT_EQ(eleInfo.is_open(), true) << "Failed to open " << infoFileName; + while (1) { + int32_t bytesCount = 0; + uint32_t flags = 0; + uint32_t timestamp = 0; + + if (!(eleInfo >> bytesCount)) break; + eleInfo >> flags; + eleInfo >> timestamp; + Info.push_back({bytesCount, flags, timestamp}); + } + if (eleInfo.is_open()) eleInfo.close(); +} + +int32_t VorbisDecoderTest::initVorbisDecoder() { + if (!mVi) { + mVi = new vorbis_info{}; + if (!mVi) return -1; + } + vorbis_info_clear(mVi); + + if (!mState) { + mState = new vorbis_dsp_state{}; + if (!mState) return -1; + } + vorbis_dsp_clear(mState); + + return 0; +} + +static void makeBitReader(const void* inputBuffer, size_t size, ogg_buffer* buf, ogg_reference* ref, + oggpack_buffer* bits) { + buf->data = (uint8_t*)inputBuffer; + buf->size = size; + buf->refcount = 1; + buf->ptr.owner = nullptr; + + ref->buffer = buf; + ref->begin = 0; + ref->length = size; + ref->next = nullptr; + + oggpack_readinit(bits, ref); +} + +void VorbisDecoderTest::processVorbisDecoder(vector<FrameInfo> Info, int32_t offset, int32_t range, + ofstream& ostrm) { + int32_t frameID = offset; + ASSERT_GE(range, 0) << "Invalid Range"; + ASSERT_GE(offset, 0) << "Invalid Offset"; + ASSERT_LT(offset, Info.size()) << "Offset must be less than "; + + while (1) { + if (frameID == Info.size() || frameID == (offset + range)) break; + int32_t size = (Info)[frameID].bytesCount; + ASSERT_GE(size, 0) << "Size for the memory allocation is negative" << size; + + if (!mInputBuffer) { + mInputBuffer = (char*)malloc(size); + ASSERT_NE(mInputBuffer, nullptr) << "Insufficient memory to read frame"; + } + + mEleStream.read(mInputBuffer, size); + ASSERT_EQ(mEleStream.gcount(), size) + << "Invalid size read. Requested: " << size << " and read: " << mEleStream.gcount(); + + int32_t numChannels = mVi->channels; + /* Decode vorbis headers only once */ + if (size > 7 && !memcmp(&mInputBuffer[1], "vorbis", 6) && + (!mInfoUnpacked || !mBooksUnpacked)) { + ASSERT_TRUE((mInputBuffer[0] == 1) || (mInputBuffer[0] == 5)) + << "unexpected type received " << mInputBuffer[0]; + ogg_buffer buf; + ogg_reference ref; + oggpack_buffer bits; + + // skip 7 <type + "vorbis"> bytes + makeBitReader((const uint8_t*)mInputBuffer + 7, size - 7, &buf, &ref, &bits); + if (mInputBuffer[0] == 1) { + vorbis_info_init(mVi); + int32_t status = _vorbis_unpack_info(mVi, &bits); + ASSERT_EQ(status, 0) << "Encountered error while unpacking info"; + if (mVi->channels != numChannels) { + ALOGV("num channels changed: %d, sample rate: %ld", mVi->channels, mVi->rate); + numChannels = mVi->channels; + } + ASSERT_FALSE(numChannels < 1 || numChannels > kMaxChannels) + << "Invalid number of channels: " << numChannels; + mInfoUnpacked = true; + } else { + ASSERT_TRUE(mInfoUnpacked) << "Data with type:5 sent before sending type:1"; + int32_t status = _vorbis_unpack_books(mVi, &bits); + ASSERT_EQ(status, 0) << "Encountered error while unpacking books"; + status = vorbis_dsp_init(mState, mVi); + ASSERT_EQ(status, 0) << "Encountered error while dsp init"; + mBooksUnpacked = true; + } + ALOGV("frameID= %d", frameID); + frameID++; + free(mInputBuffer); + mInputBuffer = nullptr; + continue; + } + + ASSERT_TRUE(mInfoUnpacked && mBooksUnpacked) + << "Missing CODEC_CONFIG data mInfoUnpacked: " << mInfoUnpacked + << " mBooksUnpack: " << mBooksUnpacked; + + int32_t numPageFrames = 0; + ASSERT_GE(size, sizeof(numPageFrames)) + << "input header has size: " << size << " expected: " << sizeof(numPageFrames); + memcpy(&numPageFrames, mInputBuffer + size - sizeof(numPageFrames), sizeof(numPageFrames)); + size -= sizeof(numPageFrames); + if (numPageFrames >= 0) { + mNumFramesLeftOnPage = numPageFrames; + } + + ogg_buffer buf; + buf.data = reinterpret_cast<unsigned char*>(mInputBuffer); + buf.size = size; + buf.refcount = 1; + buf.ptr.owner = nullptr; + + ogg_reference ref; + ref.buffer = &buf; + ref.begin = 0; + ref.length = buf.size; + ref.next = nullptr; + + ogg_packet pack; + pack.packet = &ref; + pack.bytes = ref.length; + pack.b_o_s = 0; + pack.e_o_s = 0; + pack.granulepos = 0; + pack.packetno = 0; + + size_t outCapacity = kMaxNumSamplesPerChannel * numChannels * sizeof(int16_t); + if (!mOutputBuffer) { + mOutputBuffer = (int16_t*)malloc(outCapacity); + ASSERT_NE(mOutputBuffer, nullptr) << "Insufficient memory"; + } + + int32_t numFrames = 0; + int32_t ret = vorbis_dsp_synthesis(mState, &pack, 1); + if (0 != ret) { + ALOGV("vorbis_dsp_synthesis returned %d; ignored", ret); + } else { + numFrames = vorbis_dsp_pcmout(mState, mOutputBuffer, kMaxNumSamplesPerChannel); + if (numFrames < 0) { + ALOGV("vorbis_dsp_pcmout returned %d", numFrames); + numFrames = 0; + } + } + + if (mNumFramesLeftOnPage >= 0) { + if (numFrames > mNumFramesLeftOnPage) { + ALOGV("discarding %d frames at end of page", numFrames - mNumFramesLeftOnPage); + numFrames = mNumFramesLeftOnPage; + } + mNumFramesLeftOnPage -= numFrames; + } + if (numFrames) { + int32_t outSize = numFrames * sizeof(int16_t) * numChannels; + ostrm.write(reinterpret_cast<char*>(mOutputBuffer), outSize); + } + frameID++; + free(mInputBuffer); + mInputBuffer = nullptr; + } + ALOGV("Last frame decoded = %d", frameID); +} + +TEST_P(VorbisDecoderTest, FlushTest) { + string inputFileName = gEnv->getRes() + GetParam().first; + string infoFileName = gEnv->getRes() + GetParam().second; + + vector<FrameInfo> Info; + ASSERT_NO_FATAL_FAILURE(getInfo(infoFileName, Info)); + + mEleStream.open(inputFileName, ifstream::binary); + ASSERT_EQ(mEleStream.is_open(), true) << "Failed to open " << inputFileName; + + ofstream ostrm; + ostrm.open(OUTPUT_FILE_NAME, std::ofstream::binary); + ASSERT_EQ(ostrm.is_open(), true) << "Failed to open " << OUTPUT_FILE_NAME; + + int32_t err = initVorbisDecoder(); + ASSERT_EQ(err, 0) << "initVorbisDecoder: failed to create decoder " << err; + + ASSERT_NO_FATAL_FAILURE(processVorbisDecoder(Info, 0, Info.size() / 3, ostrm)); + + // flushing the decoder + mNumFramesLeftOnPage = -1; + int32_t status = vorbis_dsp_restart(mState); + ASSERT_EQ(status, 0) << "Encountered error while restarting"; + + ASSERT_NO_FATAL_FAILURE(processVorbisDecoder(Info, (Info.size() / 3), Info.size(), ostrm)); + + ostrm.close(); + Info.clear(); +} + +TEST_P(VorbisDecoderTest, DecodeTest) { + string inputFileName = gEnv->getRes() + GetParam().first; + string infoFileName = gEnv->getRes() + GetParam().second; + + vector<FrameInfo> Info; + ASSERT_NO_FATAL_FAILURE(getInfo(infoFileName, Info)); + + mEleStream.open(inputFileName, ifstream::binary); + ASSERT_EQ(mEleStream.is_open(), true) << "Failed to open " << inputFileName; + + ofstream ostrm; + ostrm.open(OUTPUT_FILE_NAME, std::ofstream::binary); + ASSERT_EQ(ostrm.is_open(), true) << "Failed to open " << OUTPUT_FILE_NAME; + + int32_t err = initVorbisDecoder(); + ASSERT_EQ(err, 0) << "initVorbisDecoder: failed to create decoder " << err; + + ASSERT_NO_FATAL_FAILURE(processVorbisDecoder(Info, 0, Info.size(), ostrm)); + ostrm.close(); + Info.clear(); +} + +INSTANTIATE_TEST_SUITE_P( + VorbisDecoderTestAll, VorbisDecoderTest, + ::testing::Values(make_pair("bbb_vorbis_mono_64kbps_48000hz.vorbis", + "bbb_vorbis_mono_64kbps_48000hz.info"), + make_pair("bbb_vorbis_stereo_128kbps_44100hz_crypt.vorbis", + "bbb_vorbis_stereo_128kbps_44100hz_crypt.info"), + make_pair("bbb_vorbis_stereo_128kbps_48000hz.vorbis", + "bbb_vorbis_stereo_128kbps_48000hz.info"), + make_pair("bbb_vorbis_5ch_320kbps_48000hz.vorbis", + "bbb_vorbis_5ch_320kbps_48000hz.info"), + make_pair("bbb_vorbis_6ch_384kbps_24000hz.vorbis", + "bbb_vorbis_6ch_384kbps_24000hz.info"))); + +int main(int argc, char** argv) { + gEnv = new VorbisDecoderTestEnvironment(); + ::testing::AddGlobalTestEnvironment(gEnv); + ::testing::InitGoogleTest(&argc, argv); + int status = gEnv->initFromOptions(argc, argv); + if (status == 0) { + status = RUN_ALL_TESTS(); + ALOGV("Vorbis Decoder Test Result = %d", status); + } + return status; +} diff --git a/tests/VorbisDecoderTestEnvironment.h b/tests/VorbisDecoderTestEnvironment.h new file mode 100644 index 0000000..8de7524 --- /dev/null +++ b/tests/VorbisDecoderTestEnvironment.h @@ -0,0 +1,83 @@ +/* + * 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. + */ + +#ifndef __VORBIS_DECODER_TEST_ENVIRONMENT_H__ +#define __VORBIS_DECODER_TEST_ENVIRONMENT_H__ + +#include <gtest/gtest.h> + +#include <getopt.h> + +using namespace std; + +class VorbisDecoderTestEnvironment : public ::testing::Environment { + public: + VorbisDecoderTestEnvironment() : res("/data/local/tmp/"), deleteOutput(true) {} + + // Parses the command line arguments + int initFromOptions(int argc, char** argv); + + void setRes(const char* _res) { res = _res; } + + const string getRes() const { return res; } + + bool cleanUp() const { return deleteOutput; } + + private: + string res; + bool deleteOutput; +}; + +int VorbisDecoderTestEnvironment::initFromOptions(int argc, char** argv) { + static struct option options[] = {{"res", required_argument, 0, 'P'}, + {"cleanUp", optional_argument, 0, 'C'}, + {0, 0, 0, 0}}; + + while (true) { + int index = 0; + int c = getopt_long(argc, argv, "P:C:", options, &index); + if (c == -1) { + break; + } + + switch (c) { + case 'P': { + setRes(optarg); + break; + } + case 'C': + if (!strcmp(optarg, "false")) { + deleteOutput = false; + } + 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 // __VORBIS_DECODER_TEST_ENVIRONMENT_H__ |