summaryrefslogtreecommitdiff
path: root/drivers/theoraplayer/src/TheoraVideoManager.cpp
blob: 53b211374aa727b8bdf22c0206c232365cd2e47d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
/************************************************************************************
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;
}