/************************************************************************************ 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 "TheoraVideoClip.h" #include "TheoraVideoManager.h" #include "TheoraVideoFrame.h" #include "TheoraFrameQueue.h" #include "TheoraAudioInterface.h" #include "TheoraTimer.h" #include "TheoraDataSource.h" #include "TheoraUtil.h" #include "TheoraException.h" #include "core/os/memory.h" TheoraVideoClip::TheoraVideoClip(TheoraDataSource* data_source, TheoraOutputMode output_mode, int nPrecachedFrames, bool usePower2Stride): mAudioInterface(NULL), mNumDroppedFrames(0), mNumDisplayedFrames(0), mSeekFrame(-1), mDuration(-1), mNumFrames(-1), mFPS(1), mUseAlpha(0), mFrameDuration(0), mName(data_source->repr()), mStride(usePower2Stride), mSubFrameWidth(0), mSubFrameHeight(0), mSubFrameOffsetX(0), mSubFrameOffsetY(0), mAudioGain(1), mRequestedOutputMode(output_mode), mAutoRestart(0), mEndOfFile(0), mRestarted(0), mIteration(0), mPlaybackIteration(0), mStream(0), mThreadAccessCount(0), mPriority(1), mFirstFrameDisplayed(0), mWaitingForCache(false), mOutputMode(TH_UNDEFINED) { mAudioMutex = NULL; mThreadAccessMutex = new TheoraMutex(); mTimer = mDefaultTimer = new TheoraTimer(); mFrameQueue = NULL; mAssignedWorkerThread = NULL; mNumPrecachedFrames = nPrecachedFrames; setOutputMode(output_mode); } TheoraVideoClip::~TheoraVideoClip() { // wait untill a worker thread is done decoding the frame mThreadAccessMutex->lock(); delete mDefaultTimer; if (mStream) memdelete(mStream); if (mFrameQueue) delete mFrameQueue; if (mAudioInterface) { mAudioMutex->lock(); // ensure a thread isn't using this mutex memdelete(mAudioInterface); // notify audio interface it's time to call it a day mAudioMutex ->unlock(); delete mAudioMutex; } mThreadAccessMutex->unlock(); delete mThreadAccessMutex; } TheoraTimer* TheoraVideoClip::getTimer() { return mTimer; } void TheoraVideoClip::setTimer(TheoraTimer* timer) { if (!timer) mTimer = mDefaultTimer; else mTimer = timer; } void TheoraVideoClip::resetFrameQueue() { mFrameQueue->clear(); mPlaybackIteration = mIteration = 0; } void TheoraVideoClip::restart() { mEndOfFile = true; //temp, to prevent threads to decode while restarting mThreadAccessMutex->lock(); _restart(); mTimer->seek(0); mFirstFrameDisplayed = false; resetFrameQueue(); mEndOfFile = false; mRestarted = false; mSeekFrame = -1; mThreadAccessMutex->unlock(); } void TheoraVideoClip::update(float timeDelta) { if (mTimer->isPaused()) { mTimer->update(0); // update timer in case there is some code that needs to execute each frame return; } float time = mTimer->getTime(), speed = mTimer->getSpeed(); if (time + timeDelta * speed >= mDuration) { if (mAutoRestart && mRestarted) { float seekTime = time + timeDelta * speed; for (;seekTime >= mDuration;) { seekTime -= mDuration; ++mPlaybackIteration; } mTimer->seek(seekTime); } else { if (time != mDuration) { mTimer->update((mDuration - time) / speed); } } } else { mTimer->update(timeDelta); } } float TheoraVideoClip::updateToNextFrame() { TheoraVideoFrame* f = mFrameQueue->getFirstAvailableFrame(); if (!f) return 0; float time = f->mTimeToDisplay - mTimer->getTime(); update(time); return time; } TheoraFrameQueue* TheoraVideoClip::getFrameQueue() { return mFrameQueue; } void TheoraVideoClip::popFrame() { ++mNumDisplayedFrames; // after transfering frame data to the texture, free the frame // so it can be used again if (!mFirstFrameDisplayed) { mFrameQueue->lock(); mFrameQueue->_pop(1); mFirstFrameDisplayed = true; mFrameQueue->unlock(); } else { mFrameQueue->pop(); } } int TheoraVideoClip::getWidth() { return mUseAlpha ? mWidth / 2 : mWidth; } int TheoraVideoClip::getHeight() { return mHeight; } int TheoraVideoClip::getSubFrameWidth() { return mUseAlpha ? mWidth / 2 : mSubFrameWidth; } int TheoraVideoClip::getSubFrameHeight() { return mUseAlpha ? mHeight : mSubFrameHeight; } int TheoraVideoClip::getSubFrameOffsetX() { return mUseAlpha ? 0 : mSubFrameOffsetX; } int TheoraVideoClip::getSubFrameOffsetY() { return mUseAlpha ? 0 : mSubFrameOffsetY; } float TheoraVideoClip::getAbsPlaybackTime() { return mTimer->getTime() + mPlaybackIteration * mDuration; } int TheoraVideoClip::discardOutdatedFrames(float absTime) { int nReady = mFrameQueue->_getReadyCount(); // only drop frames if you have more frames to show. otherwise even the late frame will do.. if (nReady == 1) return 0; float time = absTime; int nPop = 0; TheoraVideoFrame* frame; float timeToDisplay; std::list& queue = mFrameQueue->_getFrameQueue(); foreach_l (TheoraVideoFrame*, queue) { frame = *it; if (!frame->mReady) break; timeToDisplay = frame->mTimeToDisplay + frame->mIteration * mDuration; if (time > timeToDisplay + mFrameDuration) { ++nPop; if (nReady - nPop == 1) break; // always leave at least one in the queue } else break; } if (nPop > 0) { #ifdef _DEBUG std::string log = getName() + ": dropped frame "; int i = nPop; foreach_l (TheoraVideoFrame*, queue) { log += str((int) (*it)->getFrameNumber()); if (i-- > 1) { log += ", "; } else break; } th_writelog(log); #endif mNumDroppedFrames += nPop; mFrameQueue->_pop(nPop); } return nPop; } TheoraVideoFrame* TheoraVideoClip::getNextFrame() { TheoraVideoFrame* frame; // if we are about to seek, then the current frame queue is invalidated // (will be cleared when a worker thread does the actual seek) if (mSeekFrame != -1) return NULL; mFrameQueue->lock(); float time = getAbsPlaybackTime(); discardOutdatedFrames(time); frame = mFrameQueue->_getFirstAvailableFrame(); if (frame != NULL) { if (frame->mTimeToDisplay + frame->mIteration * mDuration > time && mFirstFrameDisplayed) { frame = NULL; // frame is ready but it's not yet time to display it, except when we haven't displayed any frames yet } } mFrameQueue->unlock(); return frame; } std::string TheoraVideoClip::getName() { return mName; } bool TheoraVideoClip::isBusy() { return mAssignedWorkerThread || mOutputMode != mRequestedOutputMode; } TheoraOutputMode TheoraVideoClip::getOutputMode() { return mOutputMode; } void TheoraVideoClip::setOutputMode(TheoraOutputMode mode) { if (mode == TH_UNDEFINED) throw TheoraGenericException("Invalid output mode: TH_UNDEFINED for video: " + mName); if (mOutputMode == mode) return; mRequestedOutputMode = mode; mUseAlpha = (mode == TH_RGBA || mode == TH_ARGB || mode == TH_BGRA || mode == TH_ABGR || mode == TH_GREY3A || mode == TH_AGREY3 || mode == TH_YUVA || mode == TH_AYUV); if (mAssignedWorkerThread) { mThreadAccessMutex->lock(); // discard current frames and recreate them mFrameQueue->setSize(mFrameQueue->getSize()); mThreadAccessMutex->unlock(); } mOutputMode = mRequestedOutputMode; } float TheoraVideoClip::getTimePosition() { return mTimer->getTime(); } int TheoraVideoClip::getNumPrecachedFrames() { return mFrameQueue->getSize(); } void TheoraVideoClip::setNumPrecachedFrames(int n) { if (mFrameQueue->getSize() != n) mFrameQueue->setSize(n); } int TheoraVideoClip::_getNumReadyFrames() { if (mSeekFrame != -1) return 0; return mFrameQueue->_getReadyCount(); } int TheoraVideoClip::getNumReadyFrames() { if (mSeekFrame != -1) return 0; // we are about to seek, consider frame queue empty even though it will be emptied upon seek return mFrameQueue->getReadyCount(); } float TheoraVideoClip::getDuration() { return mDuration; } float TheoraVideoClip::getFPS() { return mFPS; } void TheoraVideoClip::play() { mTimer->play(); } void TheoraVideoClip::pause() { mTimer->pause(); } bool TheoraVideoClip::isPaused() { return mTimer->isPaused(); } bool TheoraVideoClip::isDone() { return mEndOfFile && !mFrameQueue->getFirstAvailableFrame(); } void TheoraVideoClip::stop() { pause(); resetFrameQueue(); mFirstFrameDisplayed = false; seek(0); } void TheoraVideoClip::setPlaybackSpeed(float speed) { mTimer->setSpeed(speed); } float TheoraVideoClip::getPlaybackSpeed() { return mTimer->getSpeed(); } void TheoraVideoClip::seek(float time) { seekToFrame((int) (time * getFPS())); } void TheoraVideoClip::seekToFrame(int frame) { if (frame < 0) mSeekFrame = 0; else if (frame > mNumFrames) mSeekFrame = mNumFrames; else mSeekFrame = frame; mFirstFrameDisplayed = false; mEndOfFile = false; } void TheoraVideoClip::waitForCache(float desired_cache_factor, float max_wait_time) { mWaitingForCache = true; bool paused = mTimer->isPaused(); if (!paused) mTimer->pause(); int elapsed = 0; int desired_num_precached_frames = (int) (desired_cache_factor * getNumPrecachedFrames()); while (getNumReadyFrames() < desired_num_precached_frames) { _psleep(10); elapsed += 10; if (elapsed >= max_wait_time * 1000) break; } if (!paused) mTimer->play(); mWaitingForCache = false; } float TheoraVideoClip::getPriority() { return mPriority; } void TheoraVideoClip::setPriority(float priority) { mPriority = priority; } float TheoraVideoClip::getPriorityIndex() { float priority = (float) getNumReadyFrames(); if (mTimer->isPaused()) priority += getNumPrecachedFrames() / 2; return priority; } void TheoraVideoClip::setAudioInterface(TheoraAudioInterface* iface) { mAudioInterface = iface; if (iface && !mAudioMutex) mAudioMutex = new TheoraMutex; if (!iface && mAudioMutex) { delete mAudioMutex; mAudioMutex = NULL; } } TheoraAudioInterface* TheoraVideoClip::getAudioInterface() { return mAudioInterface; } void TheoraVideoClip::setAudioGain(float gain) { if (gain > 1) mAudioGain=1; if (gain < 0) mAudioGain=0; else mAudioGain=gain; } float TheoraVideoClip::getAudioGain() { return mAudioGain; } void TheoraVideoClip::setAutoRestart(bool value) { mAutoRestart = value; if (value) mEndOfFile = false; }