diff options
Diffstat (limited to 'drivers/theoraplayer/src/TheoraVideoManager.cpp')
-rw-r--r-- | drivers/theoraplayer/src/TheoraVideoManager.cpp | 479 |
1 files changed, 479 insertions, 0 deletions
diff --git a/drivers/theoraplayer/src/TheoraVideoManager.cpp b/drivers/theoraplayer/src/TheoraVideoManager.cpp new file mode 100644 index 0000000000..87696d12a9 --- /dev/null +++ b/drivers/theoraplayer/src/TheoraVideoManager.cpp @@ -0,0 +1,479 @@ +/************************************************************************************ +This source file is part of the Theora Video Playback Library +For latest info, see http://libtheoraplayer.googlecode.com +************************************************************************************* +Copyright (c) 2008-2014 Kresimir Spes (kspes@cateia.com) +This program is free software; you can redistribute it and/or modify it under +the terms of the BSD license: http://opensource.org/licenses/BSD-3-Clause +*************************************************************************************/ +#include "TheoraVideoManager.h" +#include "TheoraWorkerThread.h" +#include "TheoraVideoClip.h" +#include "TheoraFrameQueue.h" +#include "TheoraAudioInterface.h" +#include "TheoraUtil.h" +#include "TheoraDataSource.h" +#include "TheoraException.h" +#ifdef __THEORA + #include <theora/codec.h> + #include <vorbis/codec.h> + #include "TheoraVideoClip_Theora.h" +#endif +#ifdef __AVFOUNDATION + #include "TheoraVideoClip_AVFoundation.h" +#endif +#ifdef __FFMPEG + #include "TheoraVideoClip_FFmpeg.h" +#endif +#ifdef _ANDROID //libtheoraplayer addition for cpu feature detection + #include "cpu-features.h" +#endif +// declaring function prototype here so I don't have to put it in a header file +// it only needs to be used by this plugin and called once +extern "C" +{ + void initYUVConversionModule(); +} + +//#define _DECODING_BENCHMARK //uncomment to test average decoding time on a given device + + +// -------------------------- +//#define _SCHEDULING_DEBUG +#ifdef _SCHEDULING_DEBUG +float gThreadDiagnosticTimer = 0; +#endif +// -------------------------- + +#ifdef _DECODING_BENCHMARK +void benchmark(TheoraVideoClip* clip) +{ + int nPrecached = 256; + int n = nPrecached; + char msg[1024]; + clock_t t = clock(); + while (n > 0) + { + clip->waitForCache(1.0f, 1000000); + n -= 32; + clip->getFrameQueue()->clear(); + } + float diff = ((float) (clock() - t) * 1000.0f) / CLOCKS_PER_SEC; + sprintf(msg, "BENCHMARK: %s: Decoding %d frames took %.1fms (%.2fms average per frame)\n",clip->getName().c_str(), nPrecached, diff, diff / nPrecached); + TheoraVideoManager::getSingleton().logMessage(msg); + clip->seek(0); +} +#endif + +struct TheoraWorkCandidate +{ + TheoraVideoClip* clip; + float priority, queuedTime, workTime, entitledTime; +}; + +TheoraVideoManager* g_ManagerSingleton = NULL; + +void theora_writelog(std::string output) +{ + printf("%s\n", output.c_str()); +} + +void (*g_LogFuction)(std::string) = theora_writelog; + +void TheoraVideoManager::setLogFunction(void (*fn)(std::string)) +{ + g_LogFuction = fn; +} + +TheoraVideoManager* TheoraVideoManager::getSingletonPtr() +{ + return g_ManagerSingleton; +} + +TheoraVideoManager& TheoraVideoManager::getSingleton() +{ + return *g_ManagerSingleton; +} + +TheoraVideoManager::TheoraVideoManager(int num_worker_threads) : + mDefaultNumPrecachedFrames(8) +{ + if (num_worker_threads < 1) throw TheoraGenericException("Unable to create TheoraVideoManager, at least one worker thread is reqired"); + + g_ManagerSingleton = this; + + std::string msg = "Initializing Theora Playback Library (" + getVersionString() + ")\n"; +#ifdef __THEORA + msg += " - libtheora version: " + std::string(th_version_string()) + "\n" + + " - libvorbis version: " + std::string(vorbis_version_string()) + "\n"; +#endif +#ifdef _ANDROID + uint64_t features = android_getCpuFeaturesExt(); + char s[128]; + sprintf(s, " - Android: CPU Features: %u\n", (unsigned int) features); + msg += s; + if ((features & ANDROID_CPU_ARM_FEATURE_NEON) == 0) + msg += " - Android: NEON features NOT SUPPORTED by CPU\n"; + else + msg += " - Android: Detected NEON CPU features\n"; +#endif + +#ifdef __AVFOUNDATION + msg += " - using Apple AVFoundation classes.\n"; +#endif +#ifdef __FFMPEG + msg += " - using FFmpeg library.\n"; +#endif + + logMessage(msg + "------------------------------------"); + mAudioFactory = NULL; + mWorkMutex = new TheoraMutex(); + + // for CPU based yuv2rgb decoding + initYUVConversionModule(); + + createWorkerThreads(num_worker_threads); +} + +TheoraVideoManager::~TheoraVideoManager() +{ + destroyWorkerThreads(); + + mWorkMutex->lock(); + ClipList::iterator ci; + for (ci = mClips.begin(); ci != mClips.end(); ++ci) + delete (*ci); + mClips.clear(); + mWorkMutex->unlock(); + delete mWorkMutex; +} + +void TheoraVideoManager::logMessage(std::string msg) +{ + g_LogFuction(msg); +} + +TheoraVideoClip* TheoraVideoManager::getVideoClipByName(std::string name) +{ + TheoraVideoClip* clip = NULL; + mWorkMutex->lock(); + + foreach(TheoraVideoClip*, mClips) + { + if ((*it)->getName() == name) + { + clip = *it; + break; + } + } + mWorkMutex->unlock(); + + return clip; +} + +void TheoraVideoManager::setAudioInterfaceFactory(TheoraAudioInterfaceFactory* factory) +{ + mAudioFactory = factory; +} + +TheoraAudioInterfaceFactory* TheoraVideoManager::getAudioInterfaceFactory() +{ + return mAudioFactory; +} + +TheoraVideoClip* TheoraVideoManager::createVideoClip(std::string filename, + TheoraOutputMode output_mode, + int numPrecachedOverride, + bool usePower2Stride) +{ + TheoraDataSource* src=new TheoraFileDataSource(filename); + return createVideoClip(src,output_mode,numPrecachedOverride,usePower2Stride); +} + +TheoraVideoClip* TheoraVideoManager::createVideoClip(TheoraDataSource* data_source, + TheoraOutputMode output_mode, + int numPrecachedOverride, + bool usePower2Stride) +{ + mWorkMutex->lock(); + + TheoraVideoClip* clip = NULL; + int nPrecached = numPrecachedOverride ? numPrecachedOverride : mDefaultNumPrecachedFrames; + logMessage("Creating video from data source: " + data_source->repr() + " [" + str(nPrecached) + " precached frames]."); + +#ifdef __AVFOUNDATION + TheoraFileDataSource* fileDataSource = dynamic_cast<TheoraFileDataSource*>(data_source); + std::string filename; + if (fileDataSource == NULL) + { + TheoraMemoryFileDataSource* memoryDataSource = dynamic_cast<TheoraMemoryFileDataSource*>(data_source); + if (memoryDataSource != NULL) filename = memoryDataSource->getFilename(); + // if the user has his own data source, it's going to be a problem for AVAssetReader since it only supports reading from files... + } + else filename = fileDataSource->getFilename(); + + if (filename.size() > 4 && filename.substr(filename.size() - 4, filename.size()) == ".mp4") + { + clip = new TheoraVideoClip_AVFoundation(data_source, output_mode, nPrecached, usePower2Stride); + } +#endif +#if defined(__AVFOUNDATION) && defined(__THEORA) + else +#endif +#ifdef __THEORA + clip = new TheoraVideoClip_Theora(data_source, output_mode, nPrecached, usePower2Stride); +#endif +#ifdef __FFMPEG + clip = new TheoraVideoClip_FFmpeg(data_source, output_mode, nPrecached, usePower2Stride); +#endif + clip->load(data_source); + clip->decodeNextFrame(); // ensure the first frame is always preloaded and have the main thread do it to prevent potential thread starvatio + + mClips.push_back(clip); + mWorkMutex->unlock(); + +#ifdef _DECODING_BENCHMARK + benchmark(clip); +#endif + return clip; +} + +void TheoraVideoManager::destroyVideoClip(TheoraVideoClip* clip) +{ + if (clip) + { + th_writelog("Destroying video clip: " + clip->getName()); + mWorkMutex->lock(); + bool reported = 0; + while (clip->mAssignedWorkerThread) + { + if (!reported) + { + th_writelog(" - Waiting for WorkerThread to finish decoding in order to destroy"); + reported = 1; + } + _psleep(1); + } + if (reported) th_writelog(" - WorkerThread done, destroying..."); + + // erase the clip from the clip list + foreach (TheoraVideoClip*, mClips) + { + if ((*it) == clip) + { + mClips.erase(it); + break; + } + } + // remove all it's references from the work log + mWorkLog.remove(clip); + + // delete the actual clip + delete clip; +#ifdef _DEBUG + th_writelog("Destroyed video."); +#endif + mWorkMutex->unlock(); + } +} + +TheoraVideoClip* TheoraVideoManager::requestWork(TheoraWorkerThread* caller) +{ + if (!mWorkMutex) return NULL; + mWorkMutex->lock(); + + TheoraVideoClip* selectedClip = NULL; + float maxQueuedTime = 0, totalAccessCount = 0, prioritySum = 0, diff, maxDiff = -1; + int nReadyFrames; + std::vector<TheoraWorkCandidate> candidates; + TheoraVideoClip* clip; + TheoraWorkCandidate candidate; + + // first pass is for playing videos, but if no such videos are available for decoding + // paused videos are selected in the second pass. + // Note that paused videos that are waiting for cache are considered equal to playing + // videos in the scheduling context + + for (int i = 0; i < 2 && candidates.size() == 0; ++i) + { + foreach (TheoraVideoClip*, mClips) + { + clip = *it; + if (clip->isBusy() || (i == 0 && clip->isPaused() && !clip->mWaitingForCache)) continue; + nReadyFrames = clip->getNumReadyFrames(); + if (nReadyFrames == clip->getFrameQueue()->getSize()) continue; + + candidate.clip = clip; + candidate.priority = clip->getPriority(); + candidate.queuedTime = (float) nReadyFrames / (clip->getFPS() * clip->getPlaybackSpeed()); + candidate.workTime = (float) clip->mThreadAccessCount; + + totalAccessCount += candidate.workTime; + if (maxQueuedTime < candidate.queuedTime) maxQueuedTime = candidate.queuedTime; + + candidates.push_back(candidate); + } + } + + // prevent division by zero + if (totalAccessCount == 0) totalAccessCount = 1; + if (maxQueuedTime == 0) maxQueuedTime = 1; + + // normalize candidate values + foreach (TheoraWorkCandidate, candidates) + { + it->workTime /= totalAccessCount; + // adjust user priorities to favor clips that have fewer frames queued + it->priority *= 1.0f - (it->queuedTime / maxQueuedTime) * 0.5f; + prioritySum += it->priority; + } + foreach (TheoraWorkCandidate, candidates) + { + it->entitledTime = it->priority / prioritySum; + } + + // now, based on how much access time has been given to each clip in the work log + // and how much time should be given to each clip based on calculated priorities, + // we choose a best suited clip for this worker thread to decode next + foreach (TheoraWorkCandidate, candidates) + { + diff = it->entitledTime - it->workTime; + + if (maxDiff < diff) + { + maxDiff = diff; + selectedClip = it->clip; + } + } + + if (selectedClip) + { + selectedClip->mAssignedWorkerThread = caller; + + int nClips = (int) mClips.size(); + unsigned int maxWorkLogSize = (nClips - 1) * 50; + + if (nClips > 1) + { + mWorkLog.push_front(selectedClip); + ++selectedClip->mThreadAccessCount; + } + + TheoraVideoClip* c; + while (mWorkLog.size() > maxWorkLogSize) + { + c = mWorkLog.back(); + mWorkLog.pop_back(); + c->mThreadAccessCount--; + } +#ifdef _SCHEDULING_DEBUG + if (mClips.size() > 1) + { + int accessCount = mWorkLog.size(); + if (gThreadDiagnosticTimer > 2.0f) + { + gThreadDiagnosticTimer = 0; + std::string logstr = "-----\nTheora Playback Library debug CPU time analysis (" + str(accessCount) + "):\n"; + int percent; + foreach (TheoraVideoClip*, mClips) + { + percent = ((float) (*it)->mThreadAccessCount / mWorkLog.size()) * 100.0f; + logstr += (*it)->getName() + " (" + str((*it)->getPriority()) + "): " + str((*it)->mThreadAccessCount) + ", " + str(percent) + "%\n"; + } + logstr += "-----"; + th_writelog(logstr); + } + } +#endif + } + + mWorkMutex->unlock(); + return selectedClip; +} + +void TheoraVideoManager::update(float timeDelta) +{ + mWorkMutex->lock(); + foreach (TheoraVideoClip*, mClips) + { + (*it)->update(timeDelta); + (*it)->decodedAudioCheck(); + } + mWorkMutex->unlock(); +#ifdef _SCHEDULING_DEBUG + gThreadDiagnosticTimer += timeDelta; +#endif +} + +int TheoraVideoManager::getNumWorkerThreads() +{ + return (int) mWorkerThreads.size(); +} + +void TheoraVideoManager::createWorkerThreads(int n) +{ + TheoraWorkerThread* t; + for (int i=0;i<n;++i) + { + t=new TheoraWorkerThread(); + t->start(); + mWorkerThreads.push_back(t); + } +} + +void TheoraVideoManager::destroyWorkerThreads() +{ + foreach(TheoraWorkerThread*,mWorkerThreads) + { + (*it)->join(); + delete (*it); + } + mWorkerThreads.clear(); +} + +void TheoraVideoManager::setNumWorkerThreads(int n) +{ + if (n == getNumWorkerThreads()) return; + if (n < 1) throw TheoraGenericException("Unable to change the number of worker threads in TheoraVideoManager, at least one worker thread is reqired"); + + th_writelog("changing number of worker threats to: "+str(n)); + + destroyWorkerThreads(); + createWorkerThreads(n); +} + +std::string TheoraVideoManager::getVersionString() +{ + int a, b, c; + getVersion(&a, &b, &c); + std::string out = str(a) + "." + str(b); + if (c != 0) + { + if (c < 0) out += " RC" + str(-c); + else out += "." + str(c); + } + return out; +} + +void TheoraVideoManager::getVersion(int* a, int* b, int* c) // TODO, return a struct instead of the current solution. +{ + *a = 1; + *b = 1; + *c = 0; +} + +std::vector<std::string> TheoraVideoManager::getSupportedDecoders() +{ + std::vector<std::string> lst; +#ifdef __THEORA + lst.push_back("Theora"); +#endif +#ifdef __AVFOUNDATION + lst.push_back("AVFoundation"); +#endif +#ifdef __FFMPEG + lst.push_back("FFmpeg"); +#endif + + return lst; +} |