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

Refactored TrackWorkflow

parent 3467724d
...@@ -24,41 +24,51 @@ ...@@ -24,41 +24,51 @@
#include "ClipWorkflow.h" #include "ClipWorkflow.h"
ClipWorkflow::ClipWorkflow( Clip::Clip* clip, QMutex* renderMutex, int g_debugId = 0;
QMutex* condMutex, QWaitCondition* waitCond ) :
ClipWorkflow::ClipWorkflow( Clip::Clip* clip ) :
m_clip( clip ), m_clip( clip ),
m_buffer( NULL ), m_buffer( NULL ),
m_renderMutex( renderMutex ), m_usingBackBuffer( false ),
m_condMutex( condMutex ),
m_waitCond( waitCond ),
m_mediaPlayer(NULL), m_mediaPlayer(NULL),
m_state( ClipWorkflow::Stopped ) m_state( ClipWorkflow::Stopped ),
m_requiredState( ClipWorkflow::None )
{ {
m_buffer = new unsigned char[VIDEOHEIGHT * VIDEOWIDTH * 4]; m_buffer = new unsigned char[VIDEOHEIGHT * VIDEOWIDTH * 4];
m_backBuffer = new unsigned char[VIDEOHEIGHT * VIDEOWIDTH * 4];
m_stateLock = new QReadWriteLock; m_stateLock = new QReadWriteLock;
m_requiredStateLock = new QMutex; m_requiredStateLock = new QMutex;
m_condMutex = new QMutex;
m_waitCond = new QWaitCondition;
m_backBufferLock = new QReadWriteLock;
this->debugId = g_debugId++;
} }
ClipWorkflow::~ClipWorkflow() ClipWorkflow::~ClipWorkflow()
{ {
delete[] m_buffer; delete[] m_buffer;
delete[] m_backBuffer;
delete m_stateLock; delete m_stateLock;
delete m_requiredStateLock; delete m_requiredStateLock;
delete m_backBufferLock;
} }
unsigned char* ClipWorkflow::getOutput() unsigned char* ClipWorkflow::getOutput()
{ {
QMutexLocker lock( m_renderMutex ); QReadLocker lock( m_backBufferLock );
return m_buffer; if ( m_usingBackBuffer == true )
return m_buffer;
return m_backBuffer;
} }
void ClipWorkflow::checkStateChange() void ClipWorkflow::checkStateChange()
{ {
QMutexLocker lock( m_requiredStateLock ); QMutexLocker lock( m_requiredStateLock );
QWriteLocker lock2 ( m_stateLock ); QWriteLocker lock2( m_stateLock );
if ( m_requiredState != ClipWorkflow::None ) if ( m_requiredState != ClipWorkflow::None )
{ {
qDebug() << "Changing state"; qDebug() << "Setting required state : " << m_requiredState;
m_state = m_requiredState; m_state = m_requiredState;
m_requiredState = ClipWorkflow::None; m_requiredState = ClipWorkflow::None;
} }
...@@ -66,29 +76,42 @@ void ClipWorkflow::checkStateChange() ...@@ -66,29 +76,42 @@ void ClipWorkflow::checkStateChange()
void ClipWorkflow::lock( ClipWorkflow* clipWorkflow, void** pp_ret ) 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"; // qDebug() << "Locking in ClipWorkflow::lock";
clipWorkflow->m_renderMutex->lock(); QReadLocker lock( clipWorkflow->m_backBufferLock );
// qDebug() << clipWorkflow->getState();
*pp_ret = clipWorkflow->m_buffer; 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(); if ( cw->m_state == Rendering )
clipWorkflow->m_stateLock->lockForRead();
if ( clipWorkflow->m_state == Rendering )
{ {
QMutexLocker lock( clipWorkflow->m_condMutex ); cw->m_state = Sleeping;
clipWorkflow->m_stateLock->unlock(); cw->m_stateLock->unlock();
clipWorkflow->m_waitCond->wait( clipWorkflow->m_condMutex );
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 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"; // qDebug() << "UnLocking in ClipWorkflow::unlock";
} }
...@@ -96,6 +119,7 @@ void ClipWorkflow::setVmem() ...@@ -96,6 +119,7 @@ void ClipWorkflow::setVmem()
{ {
char buffer[32]; 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. //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()->addOption( ":vout=vmem" );
m_clip->getParent()->getVLCMedia()->setDataCtx( this ); m_clip->getParent()->getVLCMedia()->setDataCtx( this );
...@@ -146,6 +170,7 @@ void ClipWorkflow::pausedMediaPlayer() ...@@ -146,6 +170,7 @@ void ClipWorkflow::pausedMediaPlayer()
{ {
disconnect( m_mediaPlayer, SIGNAL( paused() ), this, SLOT( pausedMediaPlayer() ) ); disconnect( m_mediaPlayer, SIGNAL( paused() ), this, SLOT( pausedMediaPlayer() ) );
setState( Ready ); setState( Ready );
qDebug() << "Set Ready state";
} }
bool ClipWorkflow::isReady() const bool ClipWorkflow::isReady() const
...@@ -168,7 +193,6 @@ bool ClipWorkflow::isStopped() const ...@@ -168,7 +193,6 @@ bool ClipWorkflow::isStopped() const
ClipWorkflow::State ClipWorkflow::getState() const ClipWorkflow::State ClipWorkflow::getState() const
{ {
QReadLocker lock( m_stateLock );
return m_state; return m_state;
} }
...@@ -199,6 +223,8 @@ void ClipWorkflow::stop() ...@@ -199,6 +223,8 @@ void ClipWorkflow::stop()
qDebug() << "Stopped media player"; qDebug() << "Stopped media player";
m_mediaPlayer = NULL; m_mediaPlayer = NULL;
setState( Stopped ); setState( Stopped );
QMutexLocker lock( m_requiredStateLock );
m_requiredState = ClipWorkflow::None;
qDebug() << "Changed state"; qDebug() << "Changed state";
} }
...@@ -215,6 +241,7 @@ bool ClipWorkflow::isRendering() const ...@@ -215,6 +241,7 @@ bool ClipWorkflow::isRendering() const
void ClipWorkflow::setState( State state ) void ClipWorkflow::setState( State state )
{ {
qDebug() << "Setting state : " << state;
QWriteLocker lock( m_stateLock ); QWriteLocker lock( m_stateLock );
m_state = state; m_state = state;
} }
...@@ -225,3 +252,21 @@ void ClipWorkflow::queryStateChange( State newState ) ...@@ -225,3 +252,21 @@ void ClipWorkflow::queryStateChange( State newState )
QMutexLocker lock( m_requiredStateLock ); QMutexLocker lock( m_requiredStateLock );
m_requiredState = newState; 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 ...@@ -49,11 +49,13 @@ class ClipWorkflow : public QObject
Initializing, Initializing,
Ready, Ready,
Rendering, Rendering,
Sleeping,
Stopping,
EndReached, EndReached,
StopRequired,
}; };
int debugId;
ClipWorkflow( Clip* clip, QMutex* renderMutex, QMutex* condMutex, QWaitCondition* waitCond ); ClipWorkflow( Clip* clip );
virtual ~ClipWorkflow(); virtual ~ClipWorkflow();
/** /**
...@@ -89,6 +91,9 @@ class ClipWorkflow : public QObject ...@@ -89,6 +91,9 @@ class ClipWorkflow : public QObject
/** /**
* Returns the current workflow state. * 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; State getState() const;
...@@ -120,23 +125,53 @@ class ClipWorkflow : public QObject ...@@ -120,23 +125,53 @@ class ClipWorkflow : public QObject
*/ */
void queryStateChange( State newState ); 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: private:
static void lock( ClipWorkflow* clipWorkflow, void** pp_ret ); static void lock( ClipWorkflow* clipWorkflow, void** pp_ret );
static void unlock( ClipWorkflow* clipWorkflow ); static void unlock( ClipWorkflow* clipWorkflow );
void setVmem(); void setVmem();
void setState( State state ); void setState( State state );
/**
* Don't ever call this method from anywhere else than the unlock() method
*/
void checkStateChange(); void checkStateChange();
private: private:
Clip* m_clip; Clip* m_clip;
unsigned char* m_buffer; 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; QMutex* m_condMutex;
QWaitCondition* m_waitCond; QWaitCondition* m_waitCond;
LibVLCpp::MediaPlayer* m_mediaPlayer;
State m_state; State m_state;
QReadWriteLock* m_stateLock; QReadWriteLock* m_stateLock;
State m_requiredState; State m_requiredState;
......
...@@ -50,7 +50,6 @@ void MainWorkflow::startRender() ...@@ -50,7 +50,6 @@ void MainWorkflow::startRender()
m_currentFrame = 0; m_currentFrame = 0;
emit frameChanged( 0 ); emit frameChanged( 0 );
m_length = m_tracks[0]->getLength(); m_length = m_tracks[0]->getLength();
m_tracks[0]->startRender();
} }
unsigned char* MainWorkflow::getOutput() unsigned char* MainWorkflow::getOutput()
...@@ -67,7 +66,6 @@ void MainWorkflow::setPosition( float pos ) ...@@ -67,7 +66,6 @@ void MainWorkflow::setPosition( float pos )
if ( m_renderStarted == false ) if ( m_renderStarted == false )
return ; return ;
qint64 frame = (float)m_length * pos; qint64 frame = (float)m_length * pos;
m_tracks[0]->requirePositionChanged( pos );
m_currentFrame = frame; m_currentFrame = frame;
emit frameChanged( frame ); emit frameChanged( frame );
//Do not emit a signal for the RenderWidget, since it's the one that triggered that call... //Do not emit a signal for the RenderWidget, since it's the one that triggered that call...
......
...@@ -26,13 +26,9 @@ ...@@ -26,13 +26,9 @@
unsigned char* TrackWorkflow::blackOutput = NULL; 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_mediaPlayer = new LibVLCpp::MediaPlayer();
m_renderMutex = new QMutex;
m_requiredPositionLock = new QMutex;
if ( TrackWorkflow::blackOutput == NULL ) if ( TrackWorkflow::blackOutput == NULL )
{ {
//TODO: this ain't free ! //TODO: this ain't free !
...@@ -43,38 +39,17 @@ TrackWorkflow::TrackWorkflow() : m_requiredPosition( -1.0f ) ...@@ -43,38 +39,17 @@ TrackWorkflow::TrackWorkflow() : m_requiredPosition( -1.0f )
TrackWorkflow::~TrackWorkflow() TrackWorkflow::~TrackWorkflow()
{ {
delete m_condMutex;
delete m_waitCondition;
delete m_mediaPlayer; delete m_mediaPlayer;
delete m_renderMutex;
} }
void TrackWorkflow::addClip( Clip* clip, qint64 start ) void TrackWorkflow::addClip( Clip* clip, qint64 start )
{ {
qDebug() << "Inserting clip at frame nb" << 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 ); m_clips.insert( start, cw );
computeLength(); 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() void TrackWorkflow::computeLength()
{ {
if ( m_clips.count() == 0 ) if ( m_clips.count() == 0 )
...@@ -88,227 +63,158 @@ qint64 TrackWorkflow::getLength() const ...@@ -88,227 +63,158 @@ qint64 TrackWorkflow::getLength() const
return m_length; return m_length;
} }
bool TrackWorkflow::checkNextClip( qint64 currentFrame ) unsigned char* TrackWorkflow::renderClip( ClipWorkflow* cw, bool needRepositioning, float pos )
{ {
QMap<qint64, ClipWorkflow*>::iterator next; unsigned char* ret = TrackWorkflow::blackOutput;
const QMap<qint64, ClipWorkflow*>::const_iterator end = m_clips.end();
//Picking next clip : cw->getStateLock()->lockForRead();
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;
}
//If it's about to be used, initialize it if ( cw->getState() == ClipWorkflow::Rendering )
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 )
{ {
qDebug() << "Starting rendering"; //The rendering state meens... whell it means that the frame is
m_current = next; //beeing rendered, so we wait.
m_current.value()->startRender(); 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 ) //If frame has been rendered :
{ if ( cw->getState() == ClipWorkflow::Sleeping )
unsigned char* ret = TrackWorkflow::blackOutput;
bool clipsRemaining;
QMutexLocker lock( m_requiredPositionLock );
if ( m_requiredPosition >= 0.0f )
{ {
setPosition( m_requiredPosition ); cw->getStateLock()->unlock();
m_requiredPosition = -1.0f; ret = cw->getOutput();
if ( needRepositioning == true )
{
cw->setPosition( pos );
}
cw->wake();
} }
checkStop(); else if ( cw->getState() == ClipWorkflow::Stopped )
// qDebug() << "Frame nb" << currentFrame;
clipsRemaining = checkNextClip( currentFrame );
//This is true only before the first render.
if ( m_current == m_clips.end() )
{ {
// qDebug() << "m_current == m_clips.end()"; cw->getStateLock()->unlock();
//If the track was empty, then its end is reached cw->initialize( m_mediaPlayer );
if ( clipsRemaining == false ) cw->startRender();
emit endReached(); qDebug() << "Render started for clip" << cw->debugId;
//Else, we return a black screen.
return TrackWorkflow::blackOutput;
} }
//We proceed to the render only if the ClipWorkflow is in rendering mode. else if ( cw->getState() == ClipWorkflow::Ready ||
if ( m_current.value()->isRendering() == true ) cw->getState() == ClipWorkflow::Initializing )
{ {
m_waitCondition->wakeAll(); //If the state is Initializing, then the workflow will wait.
// qDebug() << "Is rendering == true"; //Otherwise, it will start directly.
ret = m_current.value()->getOutput(); cw->getStateLock()->unlock();
return ret; cw->startRender();
qDebug() << "Started render for clip" << cw->debugId;
} }
else if ( m_current.value()->getState() == ClipWorkflow::EndReached || else
m_current.value()->getState() == ClipWorkflow::StopRequired )
{ {
//First, we stop the current ClipWorkflow so that it won't qDebug() << "Unexpected ClipWorkflow::State when rendering:" << cw->getState();
//enter the lock/unlock cycle anymore. cw->getStateLock()->unlock();
qDebug() << "Stopping";
m_current.value()->stop();
//Then, if there's no remaining clip, end of track is reached.
if ( clipsRemaining == false )
emit endReached();
} }
// else
// qDebug() << "Uncoherent state : " << m_current.value()->getState();
return ret; return ret;
} }
void TrackWorkflow::initializeClipWorkflow( ClipWorkflow* cw ) void TrackWorkflow::preloadClip( ClipWorkflow* cw )
{ {
//>Launching the initialization cw->getStateLock()->lockForRead();
cw->initialize( m_mediaPlayer );
cw->startRender();
}
void TrackWorkflow::stopClipWorkflow( ClipWorkflow* cw ) if ( cw->getState() == ClipWorkflow::Stopped )
{
if ( cw->getState() != ClipWorkflow::Stopped && cw->getState() != ClipWorkflow::StopRequired )
{ {
cw->queryStateChange( ClipWorkflow::StopRequired ); cw->getStateLock()->unlock();
//Since state change won't be immediate, we add the clip workflow to a lookup list cw->initialize( m_mediaPlayer );
m_toStop.enqueue( cw ); return ;
} }
cw->getStateLock()->unlock();
} }
void TrackWorkflow::setPosition( float pos ) void TrackWorkflow::stopClipWorkflow( ClipWorkflow* cw )
{ {
qDebug() << "Setting pos"; cw->getStateLock()->lockForRead();
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;
if ( frame > m_length ) if ( cw->getState() == ClipWorkflow::Stopped )
{ {
if ( m_current != end ) cw->getStateLock()->unlock();
{ return ;
stopClipWorkflow( m_current.value() );
m_current = end;
qDebug() << "After end of current track";
return ;
}
} }
if ( cw->getState() == ClipWorkflow::Sleeping ||
//Locate the new clip workflow cw->getState() == ClipWorkflow::Ready ||
while ( it != end )