Commit 2cf157ac authored by Hugo Beauzee-Luyssen's avatar Hugo Beauzee-Luyssen

Refactored TrackWorkflow

parent 3467724d
......@@ -24,41 +24,51 @@
#include "ClipWorkflow.h"
ClipWorkflow::ClipWorkflow( Clip::Clip* clip, QMutex* renderMutex,
QMutex* condMutex, QWaitCondition* waitCond ) :
int g_debugId = 0;
ClipWorkflow::ClipWorkflow( Clip::Clip* clip ) :
m_clip( clip ),
m_buffer( NULL ),
m_renderMutex( renderMutex ),
m_condMutex( condMutex ),
m_waitCond( waitCond ),
m_usingBackBuffer( false ),
m_mediaPlayer(NULL),
m_state( ClipWorkflow::Stopped )
m_state( ClipWorkflow::Stopped ),
m_requiredState( ClipWorkflow::None )
{
m_buffer = new unsigned char[VIDEOHEIGHT * VIDEOWIDTH * 4];
m_backBuffer = new unsigned char[VIDEOHEIGHT * VIDEOWIDTH * 4];
m_stateLock = new QReadWriteLock;
m_requiredStateLock = new QMutex;
m_condMutex = new QMutex;
m_waitCond = new QWaitCondition;
m_backBufferLock = new QReadWriteLock;
this->debugId = g_debugId++;
}
ClipWorkflow::~ClipWorkflow()
{
delete[] m_buffer;
delete[] m_backBuffer;
delete m_stateLock;
delete m_requiredStateLock;
delete m_backBufferLock;
}
unsigned char* ClipWorkflow::getOutput()
{
QMutexLocker lock( m_renderMutex );
return m_buffer;
QReadLocker lock( m_backBufferLock );
if ( m_usingBackBuffer == true )
return m_buffer;
return m_backBuffer;
}
void ClipWorkflow::checkStateChange()
{
QMutexLocker lock( m_requiredStateLock );
QWriteLocker lock2 ( m_stateLock );
QWriteLocker lock2( m_stateLock );
if ( m_requiredState != ClipWorkflow::None )
{
qDebug() << "Changing state";
qDebug() << "Setting required state : " << m_requiredState;
m_state = m_requiredState;
m_requiredState = ClipWorkflow::None;
}
......@@ -66,29 +76,42 @@ void ClipWorkflow::checkStateChange()
void ClipWorkflow::lock( ClipWorkflow* clipWorkflow, void** pp_ret )
{
//In any case, we give vlc a buffer to render in...
//If we don't, segmentation fault will catch us and eat our brains !! ahem...
// qDebug() << "Locking in ClipWorkflow::lock";
clipWorkflow->m_renderMutex->lock();
// qDebug() << clipWorkflow->getState();
*pp_ret = clipWorkflow->m_buffer;
QReadLocker lock( clipWorkflow->m_backBufferLock );
if ( clipWorkflow->m_usingBackBuffer )
*pp_ret = clipWorkflow->m_backBuffer;
else
*pp_ret = clipWorkflow->m_buffer;
}
void ClipWorkflow::unlock( ClipWorkflow* clipWorkflow )
void ClipWorkflow::unlock( ClipWorkflow* cw )
{
clipWorkflow->m_renderMutex->unlock();
cw->m_stateLock->lockForWrite();
clipWorkflow->checkStateChange();
clipWorkflow->m_stateLock->lockForRead();
if ( clipWorkflow->m_state == Rendering )
if ( cw->m_state == Rendering )
{
QMutexLocker lock( clipWorkflow->m_condMutex );
clipWorkflow->m_stateLock->unlock();
clipWorkflow->m_waitCond->wait( clipWorkflow->m_condMutex );
cw->m_state = Sleeping;
cw->m_stateLock->unlock();
QMutexLocker lock( cw->m_condMutex );
cw->m_waitCond->wait( cw->m_condMutex );
{
QWriteLocker lock2( cw->m_backBufferLock );
cw->m_usingBackBuffer = !cw->m_usingBackBuffer;
}
cw->m_stateLock->lockForWrite();
cw->m_state = Rendering;
}
else
qDebug() << clipWorkflow->m_state;
clipWorkflow->m_stateLock->unlock();
{
qDebug() << "UnLocking. State = " << cw->m_state << "Debug Id = " << cw->debugId;
}
cw->m_stateLock->unlock();
cw->checkStateChange();
// qDebug() << "UnLocking in ClipWorkflow::unlock";
}
......@@ -96,6 +119,7 @@ void ClipWorkflow::setVmem()
{
char buffer[32];
qDebug() << "Setting vmem from clip " << this->debugId;
//TODO: it would be good if we somehow backup the old media parameters to restore it later.
m_clip->getParent()->getVLCMedia()->addOption( ":vout=vmem" );
m_clip->getParent()->getVLCMedia()->setDataCtx( this );
......@@ -146,6 +170,7 @@ void ClipWorkflow::pausedMediaPlayer()
{
disconnect( m_mediaPlayer, SIGNAL( paused() ), this, SLOT( pausedMediaPlayer() ) );
setState( Ready );
qDebug() << "Set Ready state";
}
bool ClipWorkflow::isReady() const
......@@ -168,7 +193,6 @@ bool ClipWorkflow::isStopped() const
ClipWorkflow::State ClipWorkflow::getState() const
{
QReadLocker lock( m_stateLock );
return m_state;
}
......@@ -199,6 +223,8 @@ void ClipWorkflow::stop()
qDebug() << "Stopped media player";
m_mediaPlayer = NULL;
setState( Stopped );
QMutexLocker lock( m_requiredStateLock );
m_requiredState = ClipWorkflow::None;
qDebug() << "Changed state";
}
......@@ -215,6 +241,7 @@ bool ClipWorkflow::isRendering() const
void ClipWorkflow::setState( State state )
{
qDebug() << "Setting state : " << state;
QWriteLocker lock( m_stateLock );
m_state = state;
}
......@@ -225,3 +252,21 @@ void ClipWorkflow::queryStateChange( State newState )
QMutexLocker lock( m_requiredStateLock );
m_requiredState = newState;
}
void ClipWorkflow::wake()
{
m_waitCond->wakeAll();
}
QReadWriteLock* ClipWorkflow::getStateLock()
{
return m_stateLock;
}
void ClipWorkflow::reinitialize()
{
QWriteLocker lock( m_stateLock );
m_state = Stopped;
queryStateChange( None );
}
......@@ -49,11 +49,13 @@ class ClipWorkflow : public QObject
Initializing,
Ready,
Rendering,
Sleeping,
Stopping,
EndReached,
StopRequired,
};
int debugId;
ClipWorkflow( Clip* clip, QMutex* renderMutex, QMutex* condMutex, QWaitCondition* waitCond );
ClipWorkflow( Clip* clip );
virtual ~ClipWorkflow();
/**
......@@ -89,6 +91,9 @@ class ClipWorkflow : public QObject
/**
* Returns the current workflow state.
* Be carrefull, as this function is NOT thread safe, and return the
* state without locking the state.
* It's your job to do it, by calling the getStateLock() method.
*/
State getState() const;
......@@ -120,23 +125,53 @@ class ClipWorkflow : public QObject
*/
void queryStateChange( State newState );
/**
* This method will wake the renderer thread for one iteration.
*/
void wake();
/**
* This returns the QReadWriteLock that protects the ClipWorkflow's state.
* It should be use to lock the value when checking states from outside this
* class.
*/
QReadWriteLock* getStateLock();
/**
* Put back the ClipWorkflow in its initial state.
*/
void reinitialize();
private:
static void lock( ClipWorkflow* clipWorkflow, void** pp_ret );
static void unlock( ClipWorkflow* clipWorkflow );
void setVmem();
void setState( State state );
/**
* Don't ever call this method from anywhere else than the unlock() method
*/
void checkStateChange();
private:
Clip* m_clip;
unsigned char* m_buffer;
unsigned char* m_backBuffer;
/**
* This allow the render procedure to know in which buffer it should render.
* If true, then the render occurs in the back buffer, which means the
* returned buffer much be the "front" buffer.
* In other term :
* - When m_usingBackBuffer == false, lock() will return m_buffer, and getOutput() m_backBuffer
* - When m_usingBackBuffer == true, lock() will return m_backBuffer, and getOutput() m_buffer
*/
bool m_usingBackBuffer;
QReadWriteLock* m_backBufferLock;
LibVLCpp::MediaPlayer* m_mediaPlayer;
QMutex* m_renderMutex;
QMutex* m_condMutex;
QWaitCondition* m_waitCond;
LibVLCpp::MediaPlayer* m_mediaPlayer;
State m_state;
QReadWriteLock* m_stateLock;
State m_requiredState;
......
......@@ -50,7 +50,6 @@ void MainWorkflow::startRender()
m_currentFrame = 0;
emit frameChanged( 0 );
m_length = m_tracks[0]->getLength();
m_tracks[0]->startRender();
}
unsigned char* MainWorkflow::getOutput()
......@@ -67,7 +66,6 @@ void MainWorkflow::setPosition( float pos )
if ( m_renderStarted == false )
return ;
qint64 frame = (float)m_length * pos;
m_tracks[0]->requirePositionChanged( pos );
m_currentFrame = frame;
emit frameChanged( frame );
//Do not emit a signal for the RenderWidget, since it's the one that triggered that call...
......
......@@ -26,13 +26,9 @@
unsigned char* TrackWorkflow::blackOutput = NULL;
TrackWorkflow::TrackWorkflow() : m_requiredPosition( -1.0f )
TrackWorkflow::TrackWorkflow()
{
m_condMutex = new QMutex;
m_waitCondition = new QWaitCondition;
m_mediaPlayer = new LibVLCpp::MediaPlayer();
m_renderMutex = new QMutex;
m_requiredPositionLock = new QMutex;
if ( TrackWorkflow::blackOutput == NULL )
{
//TODO: this ain't free !
......@@ -43,38 +39,17 @@ TrackWorkflow::TrackWorkflow() : m_requiredPosition( -1.0f )
TrackWorkflow::~TrackWorkflow()
{
delete m_condMutex;
delete m_waitCondition;
delete m_mediaPlayer;
delete m_renderMutex;
}
void TrackWorkflow::addClip( Clip* clip, qint64 start )
{
qDebug() << "Inserting clip at frame nb" << start;
ClipWorkflow* cw = new ClipWorkflow( clip, m_renderMutex, m_condMutex, m_waitCondition );
ClipWorkflow* cw = new ClipWorkflow( clip );
m_clips.insert( start, cw );
computeLength();
}
void TrackWorkflow::startRender()
{
m_current = m_clips.end();
if ( m_clips.size() <= 0)
return ;
//If the first frame is to be render soon, we should play it now.
if ( m_clips.begin().key() < TrackWorkflow::nbFrameBeforePreload )
{
m_clips.begin().value()->initialize( m_mediaPlayer );
if ( m_current.key() == 0 )
{
m_current = m_clips.begin();
m_current.value()->startRender();
}
}
}
void TrackWorkflow::computeLength()
{
if ( m_clips.count() == 0 )
......@@ -88,227 +63,158 @@ qint64 TrackWorkflow::getLength() const
return m_length;
}
bool TrackWorkflow::checkNextClip( qint64 currentFrame )
unsigned char* TrackWorkflow::renderClip( ClipWorkflow* cw, bool needRepositioning, float pos )
{
QMap<qint64, ClipWorkflow*>::iterator next;
const QMap<qint64, ClipWorkflow*>::const_iterator end = m_clips.end();
unsigned char* ret = TrackWorkflow::blackOutput;
//Picking next clip :
if ( m_current == end )
{
//Checking if there is a clip in the first place...
if ( m_clips.count() == 0 )
return false;
next = m_clips.begin();
}
else
{
next = m_clips.begin() + 1;
if ( next == end )
return false;
}
cw->getStateLock()->lockForRead();
//If it's about to be used, initialize it
if ( next.key() == currentFrame + TrackWorkflow::nbFrameBeforePreload )
{
//Don't do anything if the current media player is still in use
//But for it to be in use, we should have a current media :
if ( m_current != end && m_current.value()->isRendering() == false )
qDebug() << "Preloading media";
next.value()->initialize( m_mediaPlayer );
}
//This ClipWorkflow must start at this frame :
else if ( next.key() == currentFrame )
if ( cw->getState() == ClipWorkflow::Rendering )
{
qDebug() << "Starting rendering";
m_current = next;
m_current.value()->startRender();
//The rendering state meens... whell it means that the frame is
//beeing rendered, so we wait.
cw->getStateLock()->unlock();
while ( cw->isRendering() == true )
{
// qDebug() << "Waiting for complete render. State == " << cw->getState();
usleep( 100 );
}
cw->getStateLock()->lockForRead();
//This way we can trigger the appropriate if just below.
}
return true;
}
unsigned char* TrackWorkflow::getOutput( qint64 currentFrame )
{
unsigned char* ret = TrackWorkflow::blackOutput;
bool clipsRemaining;
QMutexLocker lock( m_requiredPositionLock );
if ( m_requiredPosition >= 0.0f )
//If frame has been rendered :
if ( cw->getState() == ClipWorkflow::Sleeping )
{
setPosition( m_requiredPosition );
m_requiredPosition = -1.0f;
cw->getStateLock()->unlock();
ret = cw->getOutput();
if ( needRepositioning == true )
{
cw->setPosition( pos );
}
cw->wake();
}
checkStop();
// qDebug() << "Frame nb" << currentFrame;
clipsRemaining = checkNextClip( currentFrame );
//This is true only before the first render.
if ( m_current == m_clips.end() )
else if ( cw->getState() == ClipWorkflow::Stopped )
{
// qDebug() << "m_current == m_clips.end()";
//If the track was empty, then its end is reached
if ( clipsRemaining == false )
emit endReached();
//Else, we return a black screen.
return TrackWorkflow::blackOutput;
cw->getStateLock()->unlock();
cw->initialize( m_mediaPlayer );
cw->startRender();
qDebug() << "Render started for clip" << cw->debugId;
}
//We proceed to the render only if the ClipWorkflow is in rendering mode.
if ( m_current.value()->isRendering() == true )
else if ( cw->getState() == ClipWorkflow::Ready ||
cw->getState() == ClipWorkflow::Initializing )
{
m_waitCondition->wakeAll();
// qDebug() << "Is rendering == true";
ret = m_current.value()->getOutput();
return ret;
//If the state is Initializing, then the workflow will wait.
//Otherwise, it will start directly.
cw->getStateLock()->unlock();
cw->startRender();
qDebug() << "Started render for clip" << cw->debugId;
}
else if ( m_current.value()->getState() == ClipWorkflow::EndReached ||
m_current.value()->getState() == ClipWorkflow::StopRequired )
else
{
//First, we stop the current ClipWorkflow so that it won't
//enter the lock/unlock cycle anymore.
qDebug() << "Stopping";
m_current.value()->stop();
//Then, if there's no remaining clip, end of track is reached.
if ( clipsRemaining == false )
emit endReached();
qDebug() << "Unexpected ClipWorkflow::State when rendering:" << cw->getState();
cw->getStateLock()->unlock();
}
// else
// qDebug() << "Uncoherent state : " << m_current.value()->getState();
return ret;
}
void TrackWorkflow::initializeClipWorkflow( ClipWorkflow* cw )
void TrackWorkflow::preloadClip( ClipWorkflow* cw )
{
//>Launching the initialization
cw->initialize( m_mediaPlayer );
cw->startRender();
}
cw->getStateLock()->lockForRead();
void TrackWorkflow::stopClipWorkflow( ClipWorkflow* cw )
{
if ( cw->getState() != ClipWorkflow::Stopped && cw->getState() != ClipWorkflow::StopRequired )
if ( cw->getState() == ClipWorkflow::Stopped )
{
cw->queryStateChange( ClipWorkflow::StopRequired );
//Since state change won't be immediate, we add the clip workflow to a lookup list
m_toStop.enqueue( cw );
cw->getStateLock()->unlock();
cw->initialize( m_mediaPlayer );
return ;
}
cw->getStateLock()->unlock();
}
void TrackWorkflow::setPosition( float pos )
void TrackWorkflow::stopClipWorkflow( ClipWorkflow* cw )
{
qDebug() << "Setting pos";
qint64 frame = (float)m_length * pos;
QMap<qint64, ClipWorkflow*>::iterator it = m_clips.begin();
const QMap<qint64, ClipWorkflow*>::iterator end = m_clips.end();
QMap<qint64, ClipWorkflow*>::iterator next = end;
cw->getStateLock()->lockForRead();
if ( frame > m_length )
if ( cw->getState() == ClipWorkflow::Stopped )
{
if ( m_current != end )
{
stopClipWorkflow( m_current.value() );
m_current = end;
qDebug() << "After end of current track";
return ;
}
cw->getStateLock()->unlock();
return ;
}
//Locate the new clip workflow
while ( it != end )
if ( cw->getState() == ClipWorkflow::Sleeping ||
cw->getState() == ClipWorkflow::Ready ||
cw->getState() == ClipWorkflow::EndReached )
{
if ( it.key() <= frame &&
( it.key() + it.value()->getClip()->getLength() ) > frame )
{
break;
}
else if ( next == m_clips.end() && it.key() > frame )
{
// If this clip doesn't match, but starts AFTER the frame we aim,
// we can assume that it's the next clip.
// We can break, and put it to end() in order to simulate the
// normal end of the loop.
next = it;
if ( next != m_clips.begin() )
{
next = next - 1; //Since the iterator must point to the previous video
}
else
{
next = end;
}
// in order to checkNextClip() to work.
it = end;
break ;
}
++it;
qDebug() << "Stopping from sleeping / ready / endreached state for clip " << cw->debugId;
cw->getStateLock()->unlock();
cw->queryStateChange( ClipWorkflow::Stopping );
cw->wake();
cw->stop();
}
//No clip was found, just adjusing the current clip. (Render will be black though)
if ( it == end )
else if ( cw->getState() == ClipWorkflow::Rendering )
{
qDebug() << "In black zone";
//We should use the next clip, however, we use the clip just before
//the next.
//We also stop the current clip if it was started.
if ( m_current != end )
{
stopClipWorkflow( m_current.value() );
}
//TODO: it seems that m_current may be equal to next... check if that could be a problem...
m_current = next;
qDebug() << "Stopping from rendering state for clip" << cw->debugId;
cw->getStateLock()->unlock();
while ( cw->isRendering() == true )
usleep( 100 );
qDebug() << "Rendering completed" << cw->debugId;
cw->queryStateChange( ClipWorkflow::Stopping );
cw->wake();
cw->stop();
qDebug() << "Mediaplayer Stop asked for clip" << cw->debugId;
}
// If the clip found is the current, we just change the position of the
// media player
else if ( it == m_current )
else if ( cw->getState() == ClipWorkflow::Initializing )
{
qDebug() << "Using current clip with new position";
qDebug() << it.value()->getState();
//The clip may have been stoped (if we reached end but came back at it)
if ( it.value()->isStopped() )
{
initializeClipWorkflow( it.value() );
}
it.value()->setPosition( (float)( frame - it.key() ) / (float)(it.value()->getClip()->getLength()) );
//Awaking renderers to avoid them to be stuck inside of the lock...
m_waitCondition->wakeAll();
qDebug() << "Stopping from initializing state for clip" << cw->debugId;
cw->getStateLock()->unlock();
while ( cw->isReady() == false )
usleep( 20 );
cw->stop();
}
// Else, we found a clip that is not the current one.
else
{
qDebug() << "Using other clip";
//First, we stop the current workflow.
if ( m_current != end )
{
stopClipWorkflow( m_current.value() );
}
//We initialize the new workflow
initializeClipWorkflow( it.value() );
//And this is now our current clip
m_current = it;
//TODO: we doesn't take the new position in count :/
qDebug() << "Unexpected ClipWorkflow::State when stopping :" << cw->getState();
cw->getStateLock()->unlock();
}
}
void TrackWorkflow::requirePositionChanged( float pos )
unsigned char* TrackWorkflow::getOutput( qint64 currentFrame )
{
QMutexLocker lock( m_requiredPositionLock );
m_requiredPosition = pos;
}
unsigned char* ret = TrackWorkflow::blackOutput;
QMap<qint64, ClipWorkflow*>::iterator it = m_clips.begin();
QMap<qint64, ClipWorkflow*>::iterator end = m_clips.end();
static qint64 lastFrame = 0;
bool needRepositioning;
void TrackWorkflow::checkStop()
{
while ( m_toStop.isEmpty() == false )
needRepositioning = ( abs( currentFrame - lastFrame ) > 3 ) ? true : false;
while ( it != end )
{
ClipWorkflow* cw = m_toStop.head();
if ( cw->getState() == ClipWorkflow::StopRequired )
qint64 start = it.key();
ClipWorkflow* cw = it.value();
//Is the clip supposed to render now ?
if ( start <= currentFrame && currentFrame <= start + cw->getClip()->getLength() )
{
qDebug() << "Stopping from queue";
cw->stop();