6dd8768811
-=-=-=-=-=-=-=-=-=-=- -Animation Import filter support -Animation Clip import support -Animation Optimizer Fixes, Improvements and Visibile Options -Extremely Experimental UDP support.
485 lines
13 KiB
C++
485 lines
13 KiB
C++
/************************************************************************************
|
|
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();
|
|
}
|
|
|
|
#include "core/os/memory.h"
|
|
|
|
//#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,
|
|
int p_track)
|
|
{
|
|
TheoraDataSource* src=memnew(TheoraFileDataSource(filename));
|
|
return createVideoClip(src,output_mode,numPrecachedOverride,usePower2Stride, p_track);
|
|
}
|
|
|
|
TheoraVideoClip* TheoraVideoManager::createVideoClip(TheoraDataSource* data_source,
|
|
TheoraOutputMode output_mode,
|
|
int numPrecachedOverride,
|
|
bool usePower2Stride,
|
|
int p_audio_track)
|
|
{
|
|
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->set_audio_track(p_audio_track);
|
|
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;
|
|
}
|