blob: 89cd2ca24a20fa456f65f41c34172f965e3221a2 [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 "WebmFrameThreadUnitTest"
#include <utils/Log.h>
#include <gtest/gtest.h>
#include <media/stagefright/MediaAdapter.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MetaData.h>
#include <media/stagefright/Utils.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/OpusHeader.h>
#include "webm/EbmlUtil.h"
#include "webm/WebmConstants.h"
#include "webm/WebmFrameThread.h"
using namespace android;
using namespace webm;
static constexpr int32_t kVideoIdx = 0;
static constexpr int32_t kAudioIdx = 1;
static constexpr int32_t kMaxStreamCount = 2;
static constexpr int32_t kCsdSize = 32;
static constexpr int32_t kFrameSize = 128;
static constexpr int32_t kMaxLoopCount = 20;
static constexpr int32_t kNumFramesToWrite = 32;
static constexpr int32_t kSyncFrameInterval = 10;
static constexpr uint64_t kDefaultTimeCodeScaleUs = 1000000; /* 1sec */
#define OUTPUT_FILE_NAME "/data/local/tmp/webmFrameThreadOutput.webm"
// LookUpTable of clips and metadata for component testing
static const struct InputData {
const char *mime;
int32_t firstParam;
int32_t secondParam;
bool isAudio;
} kInputData[] = {
{MEDIA_MIMETYPE_AUDIO_OPUS, 48000, 6, true},
{MEDIA_MIMETYPE_AUDIO_VORBIS, 44100, 1, true},
{MEDIA_MIMETYPE_VIDEO_VP9, 176, 144, false},
{MEDIA_MIMETYPE_VIDEO_VP8, 1920, 1080, false},
};
class WebmFrameThreadUnitTest : public ::testing::TestWithParam<std::pair<int32_t, int32_t>> {
public:
WebmFrameThreadUnitTest()
: mSinkThread(nullptr), mAudioThread(nullptr), mVideoThread(nullptr), mSource{} {}
~WebmFrameThreadUnitTest() {
if (mSinkThread) mSinkThread.clear();
if (mAudioThread) mAudioThread.clear();
if (mVideoThread) mVideoThread.clear();
}
virtual void SetUp() override {
mSegmentDataStart = 0;
mFd = open(OUTPUT_FILE_NAME, O_CREAT | O_LARGEFILE | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
ASSERT_GE(mFd, 0) << "Failed to open output file " << OUTPUT_FILE_NAME;
}
virtual void TearDown() override {
if (mFd >= 0) close(mFd);
for (int32_t idx = 0; idx < kMaxStreamCount; idx++) {
if (mSource[idx] != nullptr) {
mSource[idx].clear();
}
}
mVSink.clear();
mASink.clear();
mCuePoints.clear();
}
void addTrack(bool isAudio, int32_t index);
void writeFileData(int32_t inputFrameId, int32_t range);
void createWebmThreads(std::initializer_list<int32_t> indexList);
void startWebmFrameThreads();
void stopWebmFrameThreads();
int32_t mFd;
uint64_t mSegmentDataStart;
sp<WebmFrameSinkThread> mSinkThread;
sp<WebmFrameSourceThread> mAudioThread;
sp<WebmFrameSourceThread> mVideoThread;
List<sp<WebmElement>> mCuePoints;
sp<MediaAdapter> mSource[kMaxStreamCount];
LinkedBlockingQueue<const sp<WebmFrame>> mVSink;
LinkedBlockingQueue<const sp<WebmFrame>> mASink;
};
void writeAudioHeaderData(const sp<AMessage> &format, const char *mimeType) {
if (strncasecmp(mimeType, MEDIA_MIMETYPE_AUDIO_OPUS, strlen(MEDIA_MIMETYPE_AUDIO_OPUS) + 1) &&
strncasecmp(mimeType, MEDIA_MIMETYPE_AUDIO_VORBIS,
strlen(MEDIA_MIMETYPE_AUDIO_VORBIS) + 1)) {
ASSERT_TRUE(false) << "Unsupported mime type";
}
// Dummy CSD buffers for Opus and Vorbis
char csdBuffer[kCsdSize];
memset(csdBuffer, 0xFF, sizeof(csdBuffer));
sp<ABuffer> csdBuffer0 = ABuffer::CreateAsCopy((void *)csdBuffer, kCsdSize);
ASSERT_NE(csdBuffer0.get(), nullptr) << "Unable to allocate buffer for CSD0 data";
ASSERT_NE(csdBuffer0->base(), nullptr) << "ABuffer base is null for CSD0";
sp<ABuffer> csdBuffer1 = ABuffer::CreateAsCopy((void *)csdBuffer, kCsdSize);
ASSERT_NE(csdBuffer1.get(), nullptr) << "Unable to allocate buffer for CSD1 data";
ASSERT_NE(csdBuffer1->base(), nullptr) << "ABuffer base is null for CSD1";
sp<ABuffer> csdBuffer2 = ABuffer::CreateAsCopy((void *)csdBuffer, kCsdSize);
ASSERT_NE(csdBuffer2.get(), nullptr) << "Unable to allocate buffer for CSD2 data";
ASSERT_NE(csdBuffer2->base(), nullptr) << "ABuffer base is null for CSD2";
format->setBuffer("csd-0", csdBuffer0);
format->setBuffer("csd-1", csdBuffer1);
format->setBuffer("csd-2", csdBuffer2);
}
void WebmFrameThreadUnitTest::addTrack(bool isAudio, int32_t index) {
ASSERT_LT(index, sizeof(kInputData) / sizeof(kInputData[0]))
<< "Invalid index for loopup table";
sp<AMessage> format = new AMessage;
format->setString("mime", kInputData[index].mime);
if (!isAudio) {
format->setInt32("width", kInputData[index].firstParam);
format->setInt32("height", kInputData[index].secondParam);
} else {
format->setInt32("sample-rate", kInputData[index].firstParam);
format->setInt32("channel-count", kInputData[index].secondParam);
ASSERT_NO_FATAL_FAILURE(writeAudioHeaderData(format, kInputData[index].mime));
}
sp<MetaData> trackMeta = new MetaData;
convertMessageToMetaData(format, trackMeta);
if (!isAudio) {
mSource[kVideoIdx] = new MediaAdapter(trackMeta);
ASSERT_NE(mSource[kVideoIdx], nullptr) << "Unable to create source";
} else {
mSource[kAudioIdx] = new MediaAdapter(trackMeta);
ASSERT_NE(mSource[kAudioIdx], nullptr) << "Unable to create source";
}
}
void WebmFrameThreadUnitTest::createWebmThreads(std::initializer_list<int32_t> indexList) {
mSinkThread = new WebmFrameSinkThread(mFd, mSegmentDataStart, mVSink, mASink, mCuePoints);
ASSERT_NE(mSinkThread, nullptr) << "Failed to create Sink Thread";
bool isAudio;
// MultiTrack input
for (int32_t index : indexList) {
isAudio = kInputData[index].isAudio;
ASSERT_NO_FATAL_FAILURE(addTrack(isAudio, index));
if (!isAudio) {
mVideoThread = new WebmFrameMediaSourceThread(mSource[kVideoIdx], kVideoType, mVSink,
kDefaultTimeCodeScaleUs, 0, 0, 1, 0);
} else {
mAudioThread = new WebmFrameMediaSourceThread(mSource[kAudioIdx], kAudioType, mASink,
kDefaultTimeCodeScaleUs, 0, 0, 1, 0);
}
}
// To handle single track file
if (!mVideoThread) {
mVideoThread = new WebmFrameEmptySourceThread(kVideoType, mVSink);
} else if (!mAudioThread) {
mAudioThread = new WebmFrameEmptySourceThread(kAudioType, mASink);
}
ASSERT_NE(mVideoThread, nullptr) << "Failed to create Video Thread";
ASSERT_NE(mAudioThread, nullptr) << "Failed to create Audio Thread";
}
void WebmFrameThreadUnitTest::startWebmFrameThreads() {
status_t status = mAudioThread->start();
ASSERT_EQ(status, AMEDIA_OK) << "Failed to start Audio Thread";
status = mVideoThread->start();
ASSERT_EQ(status, AMEDIA_OK) << "Failed to start Video Thread";
status = mSinkThread->start();
ASSERT_EQ(status, AMEDIA_OK) << "Failed to start Sink Thread";
}
void WebmFrameThreadUnitTest::stopWebmFrameThreads() {
status_t status = mAudioThread->stop();
ASSERT_EQ(status, AMEDIA_OK) << "Failed to stop Audio Thread";
status = mVideoThread->stop();
ASSERT_EQ(status, AMEDIA_OK) << "Failed to stop Video Thread";
status = mSinkThread->stop();
ASSERT_EQ(status, AMEDIA_OK) << "Failed to stop Sink Thread";
}
// Write dummy data to a file
void WebmFrameThreadUnitTest::writeFileData(int32_t inputFrameId, int32_t range) {
char data[kFrameSize];
memset(data, 0xFF, sizeof(data));
int32_t status = OK;
do {
// Queue frames for both A/V tracks
for (int32_t idx = kVideoIdx; idx < kMaxStreamCount; idx++) {
sp<ABuffer> buffer = new ABuffer((void *)data, kFrameSize);
ASSERT_NE(buffer.get(), nullptr) << "ABuffer returned nullptr";
// Released in MediaAdapter::signalBufferReturned().
MediaBuffer *mediaBuffer = new MediaBuffer(buffer);
ASSERT_NE(mediaBuffer, nullptr) << "MediaBuffer returned nullptr";
mediaBuffer->add_ref();
mediaBuffer->set_range(buffer->offset(), buffer->size());
MetaDataBase &sampleMetaData = mediaBuffer->meta_data();
sampleMetaData.setInt64(kKeyTime, inputFrameId * kDefaultTimeCodeScaleUs);
// For audio codecs, treat all frame as sync frame
if ((idx == kAudioIdx) || (inputFrameId % kSyncFrameInterval == 0)) {
sampleMetaData.setInt32(kKeyIsSyncFrame, true);
}
// This pushBuffer will wait until the mediaBuffer is consumed.
if (mSource[idx] != nullptr) {
status = mSource[idx]->pushBuffer(mediaBuffer);
}
ASSERT_EQ(status, OK);
}
inputFrameId++;
} while (inputFrameId < range);
}
TEST_P(WebmFrameThreadUnitTest, WriteTest) {
int32_t index1 = GetParam().first;
int32_t index2 = GetParam().second;
ASSERT_NO_FATAL_FAILURE(createWebmThreads({index1, index2}));
ASSERT_NO_FATAL_FAILURE(startWebmFrameThreads());
ASSERT_NO_FATAL_FAILURE(writeFileData(0, kNumFramesToWrite));
if (mSource[kAudioIdx]) mSource[kAudioIdx]->stop();
if (mSource[kVideoIdx]) mSource[kVideoIdx]->stop();
ASSERT_NO_FATAL_FAILURE(stopWebmFrameThreads());
}
TEST_P(WebmFrameThreadUnitTest, PauseTest) {
int32_t index1 = GetParam().first;
int32_t index2 = GetParam().second;
ASSERT_NO_FATAL_FAILURE(createWebmThreads({index1, index2}));
ASSERT_NO_FATAL_FAILURE(startWebmFrameThreads());
int32_t offset = 0;
ASSERT_NO_FATAL_FAILURE(writeFileData(offset, kNumFramesToWrite));
offset += kNumFramesToWrite;
for (int idx = 0; idx < kMaxLoopCount; idx++) {
// pause the threads
status_t status = mAudioThread->pause();
ASSERT_EQ(status, AMEDIA_OK) << "Failed to pause Audio Thread";
status = mVideoThread->pause();
ASSERT_EQ(status, AMEDIA_OK) << "Failed to pause Video Thread";
// Under pause state, no write should happen
ASSERT_NO_FATAL_FAILURE(writeFileData(offset, kNumFramesToWrite));
offset += kNumFramesToWrite;
status = mAudioThread->resume();
ASSERT_EQ(status, AMEDIA_OK) << "Failed to resume Audio Thread";
status = mVideoThread->resume();
ASSERT_EQ(status, AMEDIA_OK) << "Failed to resume Video Thread";
ASSERT_NO_FATAL_FAILURE(writeFileData(offset, kNumFramesToWrite));
offset += kNumFramesToWrite;
}
if (mSource[kAudioIdx]) mSource[kAudioIdx]->stop();
if (mSource[kVideoIdx]) mSource[kVideoIdx]->stop();
ASSERT_NO_FATAL_FAILURE(stopWebmFrameThreads());
}
INSTANTIATE_TEST_SUITE_P(WebmFrameThreadUnitTestAll, WebmFrameThreadUnitTest,
::testing::Values(std::make_pair(0, 1), std::make_pair(0, 2),
std::make_pair(0, 3), std::make_pair(1, 0),
std::make_pair(1, 2), std::make_pair(1, 3),
std::make_pair(2, 3)));
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
int status = RUN_ALL_TESTS();
ALOGV("Test result = %d\n", status);
return status;
}