diff options
Diffstat (limited to 'src/video/testvideo.cpp')
-rw-r--r-- | src/video/testvideo.cpp | 543 |
1 files changed, 543 insertions, 0 deletions
diff --git a/src/video/testvideo.cpp b/src/video/testvideo.cpp new file mode 100644 index 0000000..0bca650 --- /dev/null +++ b/src/video/testvideo.cpp @@ -0,0 +1,543 @@ +// +// libavg - Media Playback Engine. +// Copyright (C) 2003-2014 Ulrich von Zadow +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// Current versions can be found at www.libavg.de +// + +#include "AsyncVideoDecoder.h" +#include "SyncVideoDecoder.h" +#ifdef AVG_ENABLE_VDPAU +#include "VDPAUDecoder.h" +#endif + +#include "../graphics/Filterfliprgba.h" +#include "../graphics/Filterfliprgb.h" +#include "../graphics/GraphicsTest.h" +#include "../graphics/BitmapLoader.h" + +#include "../base/StringHelper.h" +#include "../base/TimeSource.h" +#include "../base/TestSuite.h" +#include "../base/Exception.h" +#include "../base/ThreadProfiler.h" +#include "../base/Directory.h" +#include "../base/DirEntry.h" + +#include <string> +#include <sstream> +#include <cmath> + +#include <glib-object.h> + +using namespace avg; +using namespace std; +using namespace boost; + +class DecoderTest: public GraphicsTest { + public: + DecoderTest(const string& sClassName, bool bThreaded, + bool bUseHardwareAcceleration) + : GraphicsTest(sClassName+getDecoderName(bThreaded, bUseHardwareAcceleration), + 2), + m_bThreaded(bThreaded), + m_bUseHardwareAcceleration(bUseHardwareAcceleration) + {} + + protected: + bool isThreaded() + { + return m_bThreaded; + } + + bool useHardwareAcceleration() + { + return m_bUseHardwareAcceleration; + } + + VideoDecoderPtr createDecoder() + { + VideoDecoderPtr pDecoder; + if (m_bThreaded) { + pDecoder = VideoDecoderPtr(new AsyncVideoDecoder(8)); + } else { + pDecoder = VideoDecoderPtr(new SyncVideoDecoder()); + } + + return pDecoder; + } + + const AudioParams* getAudioParams() + { + static AudioParams AP(44100, 2, 256); + return &AP; + } + + int processAudioMsg(AudioMsgQueuePtr pMsgQ, AudioMsgQueuePtr pStatusQ) + { + AudioMsgPtr pMsg = pMsgQ->pop(false); + if (pMsg) { + switch (pMsg->getType()) { + case AudioMsg::AUDIO: { + AudioBufferPtr pBuffer = pMsg->getAudioBuffer(); + AudioMsgPtr pStatusMsg(new AudioMsg); + pStatusMsg->setAudioTime(pMsg->getAudioTime()); + pStatusQ->push(AudioMsgPtr(pStatusMsg)); + return pBuffer->getNumFrames(); + } + case AudioMsg::SEEK_DONE: { + AudioMsgPtr pStatusMsg(new AudioMsg); + pStatusMsg->setSeekDone(pMsg->getSeekSeqNum(), + pMsg->getSeekTime()); + pStatusQ->push(AudioMsgPtr(pStatusMsg)); + return -1; + } + default: + pStatusQ->push(pMsg); + return 0; + } + } else { + return 0; + } + } + + void processAudioSeek(AudioMsgQueuePtr pMsgQ, AudioMsgQueuePtr pStatusQ) + { + int framesDecoded = 0; + while (framesDecoded != -1) { + // The real AudioSource blocks on pMsgQ->pop() + msleep(10); + framesDecoded = processAudioMsg(pMsgQ, pStatusQ); + } + } + + virtual void testEqual(Bitmap& resultBmp, const std::string& sFName, + avg::PixelFormat pf = NO_PIXELFORMAT, float maxAverage=1.0, + float maxStdDev=1.0) + { + GraphicsTest::testEqual(resultBmp, sFName, pf, maxAverage, maxStdDev); + } + + void testEqual(Bitmap& resultBmp, Bitmap& BaselineBmp, + const std::string& sFName, float maxAverage=1.0f, float maxStdDev=1.0f) + { + GraphicsTest::testEqual(resultBmp, BaselineBmp, sFName, + maxAverage, maxStdDev); + } + + string getMediaLoc(const string& sFilename) + { + return getSrcDirName()+"../test/media/"+sFilename; + } + + private: + string getDecoderName(bool bThreaded, bool bUseHardwareAcceleration) + { + string sName = "("; + if (bThreaded) { + sName += "Threaded"; + } else { + sName += "Sync"; + } + if (bUseHardwareAcceleration) { + sName += ", VDPAU)"; + } else { + sName += ")"; + } + return sName; + } + + bool m_bThreaded; + bool m_bUseHardwareAcceleration; +}; + +class VideoDecoderTest: public DecoderTest { + public: + VideoDecoderTest(bool bThreaded, bool bUseHardwareAcceleration) + : DecoderTest("VideoDecoderTest", bThreaded, bUseHardwareAcceleration) + {} + + void runTests() + { + basicFileTest("mpeg1-48x48.mov", 30); +#ifndef AVG_ENABLE_RPI + basicFileTest("mjpeg-48x48.avi", 202); + testSeeks("mjpeg-48x48.avi"); +#else + cerr << "Skipping mjpeg tests: SW decoding too slow on RaspberryPi." << endl; +#endif + } + + private: + void basicFileTest(const string& sFilename, int expectedNumFrames) + { + try { + cerr << " Testing " << sFilename << endl; + + VideoDecoderPtr pDecoder = createDecoder(); + pDecoder->open(getMediaLoc(sFilename), useHardwareAcceleration(), true); + IntPoint frameSize = pDecoder->getSize(); + TEST(frameSize == IntPoint(48, 48)); + TEST(pDecoder->getVideoInfo().m_bHasVideo); + TEST(pDecoder->getVideoInfo().m_Duration != 0); + TEST(pDecoder->getStreamFPS() != 0); + TEST(pDecoder->getFPS() != 0); + pDecoder->startDecoding(false, getAudioParams()); + TEST(pDecoder->getPixelFormat() == B8G8R8X8); + BitmapPtr pBmp(new Bitmap(frameSize, B8G8R8X8)); + + // Test first two frames. + pDecoder->renderToBmp(pBmp, -1); + testEqual(*pBmp, sFilename+"_1", B8G8R8X8); + TEST(pDecoder->getCurFrame() == 0); + TEST(pDecoder->getCurTime() == 0); + + pDecoder->renderToBmp(pBmp, -1); + testEqual(*pBmp, sFilename+"_2", B8G8R8X8); + pDecoder->close(); + + readWholeFile(sFilename, 1, expectedNumFrames); + readWholeFile(sFilename, 0.5, expectedNumFrames); + readWholeFile(sFilename, 2, expectedNumFrames/2); + } catch (Exception & ex) { + cerr << string(m_IndentLevel+6, ' ') << ex.getStr() << endl; + throw; + } + } + + void testSeeks(const string& sFilename) + { + cerr << " Testing " << sFilename << " (seek)" << endl; + + VideoDecoderPtr pDecoder = createDecoder(); + pDecoder->open(getMediaLoc(sFilename), useHardwareAcceleration(), true); + pDecoder->startDecoding(false, getAudioParams()); + + // Seek forward + testSeek(100, sFilename, pDecoder); + // Seek backward + testSeek(53, sFilename, pDecoder); + // Seek to last frame + testSeek(201, sFilename, pDecoder); + + pDecoder->close(); + } + + void testSeek(int frameNum, const string& sFilename, VideoDecoderPtr pDecoder) + { + IntPoint frameSize = pDecoder->getSize(); + + BitmapPtr pBmp(new Bitmap(frameSize, B8G8R8X8)); + pDecoder->seek(float(frameNum)/pDecoder->getStreamFPS()); + pDecoder->renderToBmp(pBmp, -1); + testEqual(*pBmp, sFilename+"_"+toString(frameNum), B8G8R8X8); + + } + + void readWholeFile(const string& sFilename, float speedFactor, + int expectedNumFrames) + { + // Read whole file, test last image. + VideoDecoderPtr pDecoder = createDecoder(); + pDecoder->open(getMediaLoc(sFilename), useHardwareAcceleration(), true); + IntPoint frameSize = pDecoder->getSize(); + float timePerFrame = (1.0f/pDecoder->getFPS())*speedFactor; + pDecoder->startDecoding(false, getAudioParams()); + BitmapPtr pBmp(new Bitmap(frameSize, B8G8R8X8)); + int numFrames = 0; + float curTime = 0; + + while (!pDecoder->isEOF()) { + FrameAvailableCode frameAvailable = pDecoder->renderToBmp(pBmp, curTime); + if (frameAvailable == FA_NEW_FRAME) { +/* + stringstream ss; + ss << "resultimages/" << sFilename << numFrames << ".png"; + pBmp->save(ss.str()); +*/ + numFrames++; + } else { + msleep(0); + } + if (frameAvailable == FA_NEW_FRAME || frameAvailable == FA_USE_LAST_FRAME) + { + curTime += timePerFrame; + } + } +// cerr << "numFrames: " << numFrames << +// ", expectedNumFrames: " << expectedNumFrames << endl; + TEST(numFrames == expectedNumFrames); + if (speedFactor == 1 && !useHardwareAcceleration()) { + // The last frame is broken with VDPAU sometimes. Not sure why this is, + // possibly a libav bug. + testEqual(*pBmp, sFilename+"_end", B8G8R8X8); + } + + // Test loop. + pDecoder->loop(); + pDecoder->renderToBmp(pBmp, -1); + testEqual(*pBmp, sFilename+"_loop", B8G8R8X8); + + pDecoder->close(); + } + +}; + +class AudioDecoderTest: public DecoderTest { + public: + AudioDecoderTest() + : DecoderTest("AudioDecoderTest", true, false) + {} + + void runTests() + { + + testOneFile("22.050Hz_16bit_mono.wav"); + + testOneFile("44.1kHz_16bit_mono.wav"); + testOneFile("44.1kHz_16bit_stereo.wav"); + testOneFile("44.1kHz_24bit_mono.wav"); + testOneFile("44.1kHz_24bit_stereo.wav"); + + testOneFile("48kHz_16bit_mono.wav"); + testOneFile("48kHz_16bit_stereo.wav"); + testOneFile("48kHz_24bit_mono.wav"); + testOneFile("48kHz_24bit_stereo.wav"); + + testOneFile("44.1kHz_16bit_stereo.aif"); + testOneFile("44.1kHz_stereo.mp3"); + } + + private: + void testOneFile(const string& sFilename) + { + try { + cerr << " Testing " << sFilename << endl; + + { + cerr << " Reading complete file." << endl; + AsyncVideoDecoderPtr pDecoder = + dynamic_pointer_cast<AsyncVideoDecoder>(createDecoder()); + pDecoder->open(getMediaLoc(sFilename), useHardwareAcceleration(), true); + TEST(pDecoder->getVideoInfo().m_bHasAudio); + pDecoder->startDecoding(false, getAudioParams()); + AudioMsgQueuePtr pMsgQ = pDecoder->getAudioMsgQ(); + AudioMsgQueuePtr pStatusQ = pDecoder->getAudioStatusQ(); + int totalFramesDecoded = 0; + readAudioToEOF(pDecoder, pMsgQ, pStatusQ, totalFramesDecoded, true); + + // Check if we've decoded the whole file. + int framesInDuration = int(pDecoder->getVideoInfo().m_Duration*44100); +// cerr << "framesInDuration: " << framesInDuration << endl; +// cerr << "framesDecoded: " << totalFramesDecoded << endl; + TEST(abs(totalFramesDecoded-framesInDuration) < 65); + } + { + cerr << " Seek test." << endl; + AsyncVideoDecoderPtr pDecoder = + dynamic_pointer_cast<AsyncVideoDecoder>(createDecoder()); + pDecoder->open(getMediaLoc(sFilename), useHardwareAcceleration(), true); + float duration = pDecoder->getVideoInfo().m_Duration; + pDecoder->startDecoding(false, getAudioParams()); + AudioMsgQueuePtr pMsgQ = pDecoder->getAudioMsgQ(); + AudioMsgQueuePtr pStatusQ = pDecoder->getAudioStatusQ(); + pDecoder->seek(duration/2); + processAudioSeek(pMsgQ, pStatusQ); + int totalFramesDecoded = 0; + + readAudioToEOF(pDecoder, pMsgQ, pStatusQ, totalFramesDecoded, false); + if (sFilename.find(".mp3") == string::npos) { + // Check if we've decoded half the file. + // TODO: Find out why there are problems with this + // for mp3 files. + int framesInDuration = + int(pDecoder->getVideoInfo().m_Duration*44100); +// cerr << "framesDecoded: " << totalFramesDecoded << endl; +// cerr << "framesInDuration: " << framesInDuration << endl; + TEST(abs(totalFramesDecoded-framesInDuration/2) < 65); + } + } + + } catch (Exception & ex) { + cerr << string(m_IndentLevel+6, ' ') << ex.getStr() << endl; + throw; + } + } + + void readAudioToEOF(AsyncVideoDecoderPtr pDecoder, AudioMsgQueuePtr pMsgQ, + AudioMsgQueuePtr pStatusQ, int& totalFramesDecoded, + bool bCheckTimestamps) + { + int numWrongTimestamps = 0; + while (!pDecoder->isEOF()) { + int framesDecoded = 0; + while (framesDecoded == 0 && !pDecoder->isEOF()) { + framesDecoded = processAudioMsg(pMsgQ, pStatusQ); + AVG_ASSERT(framesDecoded != -1); + pDecoder->updateAudioStatus(); + msleep(0); + } + totalFramesDecoded += framesDecoded; + float curTime = float(totalFramesDecoded)/44100; + if (abs(curTime-pDecoder->getCurTime()) > 0.02f) { + numWrongTimestamps++; + } + } + if (bCheckTimestamps) { + if (numWrongTimestamps>0) { + TEST_FAILED(numWrongTimestamps << " wrong timestamps."); + } + } + } +}; + + +class AVDecoderTest: public DecoderTest { + public: + AVDecoderTest(bool bUseHardwareAcceleration) + : DecoderTest("AVDecoderTest", true, bUseHardwareAcceleration) + {} + + void runTests() + { + basicFileTest("mpeg1-48x48-sound.avi", 30); + } + + private: + void basicFileTest(const string& sFilename, int expectedNumFrames) + { + VideoDecoderPtr pDecoder = createDecoder(); + pDecoder->open(getMediaLoc(sFilename), useHardwareAcceleration(), true); + TEST(pDecoder->getVideoInfo().m_bHasVideo); + TEST(pDecoder->getStreamFPS() != 0); + pDecoder->startDecoding(false, getAudioParams()); + AudioMsgQueuePtr pMsgQ; + AudioMsgQueuePtr pStatusQ; + + pMsgQ = dynamic_pointer_cast<AsyncVideoDecoder>(pDecoder) ->getAudioMsgQ(); + pStatusQ = dynamic_pointer_cast<AsyncVideoDecoder>(pDecoder) + ->getAudioStatusQ(); + TEST(pDecoder->getVideoInfo().m_bHasAudio); + + IntPoint frameSize = pDecoder->getSize(); + BitmapPtr pBmp(new Bitmap(frameSize, B8G8R8X8)); + int numFrames = 0; + int totalFramesDecoded = 0; + float curTime = 0; + + while (!pDecoder->isEOF()) { + FrameAvailableCode frameAvailable; + do { + frameAvailable = pDecoder->renderToBmp(pBmp, curTime); + msleep(0); + } while (frameAvailable == FA_STILL_DECODING); + if (frameAvailable == FA_NEW_FRAME) { +// stringstream ss; +// ss << sFilename << numFrames << ".png"; +// pBmp->save(ss.str()); + numFrames++; + } + int framesDecoded = 0; + while (framesDecoded == 0 && !pDecoder->isEOF()) { + framesDecoded = processAudioMsg(pMsgQ, pStatusQ); + dynamic_pointer_cast<AsyncVideoDecoder>(pDecoder) + ->updateAudioStatus(); + msleep(0); + } + totalFramesDecoded += framesDecoded; + + curTime += 1.0f/pDecoder->getFPS(); + } + TEST(pDecoder->isEOF()); + TEST(numFrames == expectedNumFrames); + testEqual(*pBmp, sFilename+"_end", B8G8R8X8); + + // Test loop. + pDecoder->seek(0); + processAudioSeek(pMsgQ, pStatusQ); + pDecoder->renderToBmp(pBmp, -1); + testEqual(*pBmp, sFilename+"_loop", B8G8R8X8); + + pDecoder->close(); + } +}; + + +class VideoTestSuite: public TestSuite { +public: + VideoTestSuite() + : TestSuite("VideoTestSuite") + { + addAudioTests(); + addVideoTests(false); +#ifdef AVG_ENABLE_VDPAU + if (VDPAUDecoder::isAvailable()) { + addVideoTests(true); + } else { + cerr << "Skipping VDPAU tests: VDPAU configured but not available." << endl; + } +#else + cerr << "Skipping VDPAU tests: VDPAU not configured." << endl; +#endif + } +private: + + void addAudioTests() + { + addTest(TestPtr(new AudioDecoderTest())); + } + + void addVideoTests(bool bUseHardwareAcceleration) + { + addTest(TestPtr(new VideoDecoderTest(false, bUseHardwareAcceleration))); + addTest(TestPtr(new VideoDecoderTest(true, bUseHardwareAcceleration))); + + addTest(TestPtr(new AVDecoderTest(bUseHardwareAcceleration))); + } +}; + + +int main(int nargs, char** args) +{ + ThreadProfiler* pProfiler = ThreadProfiler::get(); + pProfiler->setName("main"); + + GraphicsTest::createResultImgDir(); + + BitmapLoader::init(true); + VideoTestSuite suite; + bool bOk; + + suite.runTests(); + bOk = suite.isOk(); +/* + for (int i=0; i<300; ++i) { +// while(true) { + suite.runTests(); + bOk = suite.isOk(); + if (!bOk) { + return 1; + } + } +*/ + if (bOk) { + return 0; + } else { + return 1; + } +} + |