/* * 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 #include #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 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> { 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 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& 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 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 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(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(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 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 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; }