TrackWorkflow.cpp 18.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
/*****************************************************************************
 * TrackWorkflow.cpp : Will query the Clip workflow for each successive clip in the track
 *****************************************************************************
 * Copyright (C) 2008-2009 the VLMC team
 *
 * Authors: Hugo Beauzee-Luyssen <hugo@vlmc.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#include <QtDebug>

25
#include "vlmc.h"
26
#include "TrackWorkflow.h"
27
#include "VideoClipWorkflow.h"
28

29
TrackWorkflow::TrackWorkflow( unsigned int trackId, TrackWorkflow::TrackType type  ) :
30
        m_trackId( trackId ),
31
        m_length( 0 ),
32
        m_forceRepositionning( false ),
33
        m_paused( false ),
34 35
        m_synchroneRenderBuffer( NULL ),
        m_trackType( type )
36
{
37
    m_forceRepositionningMutex = new QMutex;
38
    m_clipsLock = new QReadWriteLock;
39 40
}

41 42
TrackWorkflow::~TrackWorkflow()
{
43 44
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();
45 46 47 48 49 50 51

    while ( it != end )
    {
        stopClipWorkflow( it.value() );
        delete it.value();
        it = m_clips.erase( it );
    }
52
    delete m_clipsLock;
53
    delete m_forceRepositionningMutex;
54 55
}

56
void    TrackWorkflow::addClip( Clip* clip, qint64 start )
57
{
58 59 60
    ClipWorkflow* cw;
    if ( m_trackType == TrackWorkflow::Video )
        cw = new VideoClipWorkflow( clip );
61
    addClip( cw, start );
62 63
}

64 65 66
void    TrackWorkflow::addClip( ClipWorkflow* cw, qint64 start )
{
    QWriteLocker    lock( m_clipsLock );
67
    connect( cw, SIGNAL( renderComplete( ClipWorkflow* ) ), this, SLOT( clipWorkflowRenderCompleted( ClipWorkflow* ) ), Qt::DirectConnection );
68 69
    connect( cw, SIGNAL( paused() ), this, SLOT( clipWorkflowPaused() ) );
    connect( cw, SIGNAL( unpaused() ), this, SLOT( clipWorkflowUnpaused() ) );
70 71 72 73
    m_clips.insert( start, cw );
    computeLength();
}

74
//Must be called from a thread safe method (m_clipsLock locked)
75 76 77
void                TrackWorkflow::computeLength()
{
    if ( m_clips.count() == 0 )
78
    {
79
        m_length = 0;
80 81
        return ;
    }
82
    QMap<qint64, ClipWorkflow*>::const_iterator it = m_clips.end() - 1;
83 84 85 86 87 88 89 90
    m_length = (it.key() + it.value()->getClip()->getLength() );
}

qint64              TrackWorkflow::getLength() const
{
    return m_length;
}

91
qint64              TrackWorkflow::getClipPosition( const QUuid& uuid ) const
92
{
93 94
    QMap<qint64, ClipWorkflow*>::const_iterator     it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::const_iterator     end = m_clips.end();
95 96 97

    while ( it != end )
    {
98
        if ( it.value()->getClip()->getUuid() == uuid )
99 100 101 102
            return it.key();
        ++it;
    }
    return -1;
103 104
}

105 106 107 108 109 110 111
Clip*               TrackWorkflow::getClip( const QUuid& uuid )
{
    QMap<qint64, ClipWorkflow*>::const_iterator     it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::const_iterator     end = m_clips.end();

    while ( it != end )
    {
112
        if ( it.value()->getClip()->getUuid() == uuid )
113 114 115 116 117 118
            return it.value()->getClip();
        ++it;
    }
    return NULL;
}

119
void        TrackWorkflow::renderClip( ClipWorkflow* cw, qint64 currentFrame,
120
                                        qint64 start , bool needRepositioning )
121
{
122
    cw->getStateLock()->lockForRead();
123

Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
124
//    qDebug() << "Rendering clip" << cw << "state:" << cw->getState();
125
    if ( cw->getState() == ClipWorkflow::Rendering )
126
    {
127 128 129
        //The rendering state meens... whell it means that the frame is
        //beeing rendered, so we wait.
        cw->getStateLock()->unlock();
130
        cw->waitForCompleteRender();
131
        //This way we can trigger the appropriate if just below.
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
132 133 134
        //by restoring the initial state of the function, and just pretend that
        //nothing happened.
        cw->getStateLock()->lockForRead();
135
    }
136

137
    //If frame has been rendered :
138
    if ( cw->getState() == ClipWorkflow::Sleeping )
139
    {
140
        cw->getStateLock()->unlock();
141

142
        if ( needRepositioning == true )
143
            adjustClipTime( currentFrame, start, cw );
144
        QMutexLocker    lock( cw->getSleepMutex() );
145
        cw->wake();
146
    }
147
    else if ( cw->getState() == ClipWorkflow::Stopped )
148
    {
149
        cw->getStateLock()->unlock();
150 151
        cw->initialize();
        cw->startRender( m_paused );
152
        if ( start != currentFrame || cw->getClip()->getBegin() != 0 ) //Clip was not started as its real begining
153
        {
154
            adjustClipTime( currentFrame, start, cw );
155
        }
156 157
        if ( m_paused == true )
            clipWorkflowRenderCompleted( cw );
158
    }
159 160
    else if ( cw->getState() == ClipWorkflow::Ready ||
              cw->getState() == ClipWorkflow::Initializing )
161
    {
162 163 164
        //If the state is Initializing, then the workflow will wait.
        //Otherwise, it will start directly.
        cw->getStateLock()->unlock();
165
        cw->startRender( false );
166

167 168
        if ( needRepositioning == true )
        {
169
            adjustClipTime( currentFrame, start, cw );
170
        }
171
    }
172 173 174
    else if ( cw->getState() == ClipWorkflow::EndReached )
    {
        cw->getStateLock()->unlock();
175
        clipWorkflowRenderCompleted( cw );
176 177
        //The stopClipWorkflow() method will take care of that.
    }
178 179 180 181 182
    else if ( cw->getState() == ClipWorkflow::Paused )
    {
        cw->getStateLock()->unlock();
        if ( needRepositioning == true )
        {
183
            adjustClipTime( currentFrame, start, cw );
184 185 186
        }
        clipWorkflowRenderCompleted( cw );
    }
187
    else
188
    {
189
        cw->getStateLock()->unlock();
190
    }
191
}
192

193
void                TrackWorkflow::preloadClip( ClipWorkflow* cw )
194
{
195
    cw->getStateLock()->lockForRead();
196

197
    if ( cw->getState() == ClipWorkflow::Stopped )
198
    {
199
        cw->getStateLock()->unlock();
200
        cw->initialize( true );
201
        return ;
202
    }
203
    cw->getStateLock()->unlock();
204 205
}

206
void                TrackWorkflow::stopClipWorkflow( ClipWorkflow* cw )
207
{
208
    cw->getStateLock()->lockForRead();
209

210
    if ( cw->getState() == ClipWorkflow::Stopped )
211
    {
212 213
        cw->getStateLock()->unlock();
        return ;
214
    }
215 216 217
    if ( cw->getState() == ClipWorkflow::Sleeping ||
         cw->getState() == ClipWorkflow::Ready ||
         cw->getState() == ClipWorkflow::EndReached )
218
    {
219
        cw->getStateLock()->unlock();
220 221 222 223
        {
            QMutexLocker    lock( cw->getSleepMutex() );
            cw->queryStateChange( ClipWorkflow::Stopping );
        }
224 225
        cw->wake();
        cw->stop();
226
    }
227
    else if ( cw->getState() == ClipWorkflow::Rendering )
228
    {
229
        cw->getStateLock()->unlock();
230
        cw->waitForCompleteRender();
231 232 233 234
        {
            QMutexLocker    lock( cw->getSleepMutex() );
            cw->queryStateChange( ClipWorkflow::Stopping );
        }
235 236
        cw->wake();
        cw->stop();
237
    }
238
    else if ( cw->getState() == ClipWorkflow::Initializing )
239
    {
240
        cw->getStateLock()->unlock();
241
        cw->waitForCompleteInit();
242
        cw->stop();
243
    }
244 245 246 247
    else if ( cw->getState() == ClipWorkflow::Paused )
    {
        cw->getStateLock()->unlock();
        cw->queryStateChange( ClipWorkflow::Stopping );
248
        cw->wake();
249 250
        cw->stop();
    }
251 252
    else
    {
253
        qDebug() << "Unexpected ClipWorkflow::State when stopping :" << cw->getState();
254
        cw->getStateLock()->unlock();
255 256 257
    }
}

258 259 260 261 262 263 264 265 266 267
bool                TrackWorkflow::checkEnd( qint64 currentFrame ) const
{
    if ( m_clips.size() == 0 )
        return true;
    //This is the last video by chronological order :
    QMap<qint64, ClipWorkflow*>::const_iterator   it = m_clips.end() - 1;
    //If it ends before the current frame, we reached end.
    return ( it.value()->getClip()->getLength() + it.key() < currentFrame );
}

268 269
void                    TrackWorkflow::stop()
{
270 271
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();
272 273 274 275 276 277 278 279

    while ( it != end )
    {
        stopClipWorkflow( it.value() );
        ++it;
    }
}

280
bool                TrackWorkflow::getOutput( qint64 currentFrame )
281
{
282 283
    QReadLocker     lock( m_clipsLock );

284 285 286 287 288
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();
    static  qint64                              lastFrame = 0;
    bool                                        needRepositioning;
    bool                                        hasRendered = false;
289

290 291 292
    if ( checkEnd( currentFrame ) == true )
    {
        emit trackEndReached( m_trackId );
293
        //We continue, as there can be ClipWorkflow that requires to be stopped.
294
    }
295 296 297 298 299 300 301
    {
        QMutexLocker    lock( m_forceRepositionningMutex );
        if ( m_forceRepositionning == true )
        {
            needRepositioning = true;
            m_forceRepositionning = false;
        }
302
        else if ( m_paused == true && currentFrame != lastFrame )
303
            needRepositioning = true;
304
        else
305
            needRepositioning = ( abs( currentFrame - lastFrame ) > 1 ) ? true : false;
306
    }
307
    m_nbClipToRender = 0;
308

309
    while ( it != end )
310
    {
311 312 313
        qint64          start = it.key();
        ClipWorkflow*   cw = it.value();
        //Is the clip supposed to render now ?
314
//        qDebug() << "Start:" << start << "Current Frame:" << currentFrame;
315
        if ( start <= currentFrame && currentFrame <= start + cw->getClip()->getLength() )
316
        {
317
            m_nbClipToRender.fetchAndAddAcquire( 1 );
318
            renderClip( cw, currentFrame, start, needRepositioning );
319
            hasRendered = true;
320
        }
321 322 323
        //Is it about to be rendered ?
        else if ( start > currentFrame &&
                start - currentFrame < TrackWorkflow::nbFrameBeforePreload )
324
        {
325
            preloadClip( cw );
326
        }
327 328
        //Is it supposed to be stopped ?
        else
329
        {
330
            stopClipWorkflow( cw );
331
        }
332 333

        ++it;
334
    }
335
    if ( hasRendered == false )
336 337 338
    {
        clipWorkflowRenderCompleted( NULL );
    }
339
    lastFrame = currentFrame;
340
    return hasRendered;
341 342
}

343
void                TrackWorkflow::pause()
344 345 346
{
    QReadLocker     lock( m_clipsLock );

347 348 349
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();
    bool                                        pauseRequired = false;
350

351
    m_nbClipToPause = 0;
352
    for ( ; it != end; ++it )
353 354 355
    {
        ClipWorkflow*   cw = it.value();

356
        cw->getStateLock()->lockForRead();
357 358 359 360 361
        if ( cw->getState() == ClipWorkflow::Stopped )
        {
            cw->getStateLock()->unlock();
            continue ;
        }
362
        else if ( cw->getState() != ClipWorkflow::Paused )
363
        {
364
            cw->getStateLock()->unlock();
365
            m_nbClipToPause.fetchAndAddAcquire( 1 );
366
            cw->pause();
367
            pauseRequired = true;
368 369 370
        }
        else
        {
371
            //This should never be used.
372
            qDebug() << "Asking to pause in an already paused state";
373
            cw->getStateLock()->unlock();
374 375
        }
    }
376 377 378 379
    if ( pauseRequired == false )
    {
        clipWorkflowPaused();
    }
380 381
}

382
void            TrackWorkflow::moveClip( const QUuid& id, qint64 startingFrame )
383
{
384 385
    QWriteLocker    lock( m_clipsLock );

386 387
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();
388 389 390

    while ( it != end )
    {
391
        if ( it.value()->getClip()->getUuid() == id )
392 393 394 395 396 397
        {
            ClipWorkflow* cw = it.value();
            m_clips.erase( it );
            m_clips[startingFrame] = cw;
            QMutexLocker    lock( m_forceRepositionningMutex );
            m_forceRepositionning = true;
398
            computeLength();
399 400 401 402 403 404 405
            return ;
        }
        ++it;
    }
    qDebug() << "Track" << m_trackId << "was asked to move clip" << id << "to position" << startingFrame
            << "but this clip doesn't exist in this track";
}
406 407 408 409 410

Clip*       TrackWorkflow::removeClip( const QUuid& id )
{
    QWriteLocker    lock( m_clipsLock );

411 412
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();
413 414 415

    while ( it != end )
    {
416
        if ( it.value()->getClip()->getUuid() == id )
417 418
        {
            ClipWorkflow*   cw = it.value();
419
            Clip*           clip = cw->getClip();
420
            m_clips.erase( it );
421
            computeLength();
422
            disconnectClipWorkflow( cw );
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
423
            delete cw;
424
            return clip;
425 426 427 428 429
        }
        ++it;
    }
    return NULL;
}
430

431 432 433 434 435 436 437 438 439 440 441 442
ClipWorkflow*       TrackWorkflow::removeClipWorkflow( const QUuid& id )
{
    QWriteLocker    lock( m_clipsLock );

    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();

    while ( it != end )
    {
        if ( it.value()->getClip()->getUuid() == id )
        {
            ClipWorkflow*   cw = it.value();
443
            disconnectClipWorkflow( cw );
444 445 446 447 448 449 450 451 452 453
            m_clips.erase( it );
            computeLength();
            return cw;

        }
        ++it;
    }
    return NULL;
}

454 455
void        TrackWorkflow::clipWorkflowRenderCompleted( ClipWorkflow* cw )
{
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
456
//    qDebug() << "Clip [" << QObject::sender() << "] render is completed on track" << m_trackId;
457
    if ( cw != NULL )
458
    {
459
        m_synchroneRenderBuffer = reinterpret_cast<unsigned char*>( cw->getOutput() );
460
    }
461
    else
462
    {
463
        m_synchroneRenderBuffer = NULL;
464
    }
465
    m_nbClipToRender.fetchAndAddAcquire( -1 );
466 467 468
    //When there is nothing to render, m_nbClipToRender will be equal to one here, so we check for minus
    //or equal to 0
    if ( m_nbClipToRender <= 0 )
469
    {
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
470
//        qDebug() << "Track render completed";
471
        emit renderCompleted( m_trackId );
472
    }
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
473 474
//    else
//s        qDebug() << "Track render not completed yet";
475
}
476 477 478 479 480

unsigned char*  TrackWorkflow::getSynchroneOutput()
{
    return m_synchroneRenderBuffer;
}
481 482 483 484 485 486

void    TrackWorkflow::clipWorkflowPaused()
{
    m_nbClipToPause.fetchAndAddAcquire( -1 );
    if ( m_nbClipToPause <= 0 )
    {
487
        m_paused = true;
488 489 490 491 492 493 494 495
        emit trackPaused();
    }
}

void    TrackWorkflow::unpause()
{
    QReadLocker     lock( m_clipsLock );

496 497
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();
498
    bool                                        unpauseRequired = false;
499 500 501 502 503 504 505 506 507 508 509 510

    m_nbClipToUnpause = 0;
    for ( ; it != end; ++it )
    {
        ClipWorkflow*   cw = it.value();

        cw->getStateLock()->lockForRead();
        if ( cw->getState() == ClipWorkflow::Paused )
        {
            cw->getStateLock()->unlock();
            m_nbClipToUnpause.fetchAndAddAcquire( 1 );
            cw->unpause();
511
            unpauseRequired = true;
512 513 514 515 516 517
        }
        else
        {
            cw->getStateLock()->unlock();
        }
    }
518 519
    if ( unpauseRequired == false )
        clipWorkflowUnpaused();
520 521 522 523 524 525 526
}

void    TrackWorkflow::clipWorkflowUnpaused()
{
    m_nbClipToUnpause.fetchAndAddAcquire( -1 );
    if ( m_nbClipToUnpause <= 0 )
    {
527
        m_paused = false;
528 529 530
        emit trackUnpaused();
    }
}
531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574

void    TrackWorkflow::save( QDomDocument& doc, QDomElement& trackNode ) const
{
    QReadLocker     lock( m_clipsLock );

    QMap<qint64, ClipWorkflow*>::const_iterator     it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::const_iterator     end = m_clips.end();

    for ( ; it != end ; ++it )
    {
        QDomElement     clipNode = doc.createElement( "clip" );

        {
            QDomElement     parent = doc.createElement( "parent" );

            QDomCharacterData   text = doc.createTextNode( it.value()->getClip()->getParent()->getUuid().toString() );
            parent.appendChild( text );
            clipNode.appendChild( parent );
        }
        {
            QDomElement     startFrame = doc.createElement( "startFrame" );

            QDomCharacterData   text = doc.createTextNode( QString::number( it.key() ) );
            startFrame.appendChild( text );
            clipNode.appendChild( startFrame );
        }
        {
            QDomElement     begin = doc.createElement( "begin" );

            QDomCharacterData   text = doc.createTextNode( QString::number( it.value()->getClip()->getBegin() ) );
            begin.appendChild( text );
            clipNode.appendChild( begin );
        }
        {
            QDomElement     end = doc.createElement( "end" );

            QDomCharacterData   text = doc.createTextNode( QString::number( it.value()->getClip()->getEnd() ) );
            end.appendChild( text );
            clipNode.appendChild( end );
        }
        trackNode.appendChild( clipNode );
    }
}

575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
void    TrackWorkflow::clear()
{
    QWriteLocker    lock( m_clipsLock );
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();

    for ( ; it != end; ++it )
    {
        ClipWorkflow*   cw = it.value();
        delete cw->getClip();
        delete cw;
    }
    m_clips.clear();
    m_length = 0;
}
590 591 592 593

void    TrackWorkflow::adjustClipTime( qint64 currentFrame, qint64 start, ClipWorkflow* cw )
{
    qint64  nbMs = ( currentFrame - start ) / cw->getClip()->getParent()->getFps() * 1000;
594 595
    qint64  beginInMs = cw->getClip()->getBegin() / cw->getClip()->getParent()->getFps() * 1000;
    qint64  startFrame = beginInMs + nbMs;
596 597
    cw->setTime( startFrame );
}
598 599 600 601 602 603 604 605 606 607 608 609

void        TrackWorkflow::setFullSpeedRender( bool value )
{
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();

    while ( it != end )
    {
        it.value()->setFullSpeedRender( value );
        ++it;
    }
}
610 611 612 613 614 615 616

void    TrackWorkflow::disconnectClipWorkflow( ClipWorkflow* cw )
{
    disconnect( cw, SIGNAL( renderComplete( ClipWorkflow* ) ), this, SLOT( clipWorkflowRenderCompleted( ClipWorkflow* ) ) );
    disconnect( cw, SIGNAL( paused() ), this, SLOT( clipWorkflowPaused() ) );
    disconnect( cw, SIGNAL( unpaused() ), this, SLOT( clipWorkflowUnpaused() ) );
}
617 618 619 620 621 622

void    TrackWorkflow::forceRepositionning()
{
    QMutexLocker    lock( m_forceRepositionningMutex );
    m_forceRepositionning = true;
}