MainWorkflow.cpp 14.4 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 25 26 27
/*****************************************************************************
 * MainWorkflow.cpp : Will query all of the track workflows to render the final
 *                    image
 *****************************************************************************
 * 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>

#include "MainWorkflow.h"

28
unsigned char*  MainWorkflow::blackOutput = NULL;
29
MainWorkflow*   MainWorkflow::m_instance = NULL;
30

31
MainWorkflow::MainWorkflow( int trackCount ) :
32
        m_length( 0 ),
33 34
        m_trackCount( trackCount ),
        m_renderStarted( false )
35
{
36 37 38 39 40 41
    Q_ASSERT_X( MainWorkflow::m_instance == NULL,
                "MainWorkflow constructor", "Can't have more than one MainWorkflow instance" );
    m_instance = this;

    MainWorkflow::blackOutput = new unsigned char[VIDEOHEIGHT * VIDEOWIDTH * 3];
    memset( MainWorkflow::blackOutput, 0, VIDEOHEIGHT * VIDEOWIDTH * 3 );
42 43

    m_tracks = new Toggleable<TrackWorkflow*>[trackCount];
44
    for ( int i = 0; i < trackCount; ++i )
45 46 47
    {
        m_tracks[i].setPtr( new TrackWorkflow( i ) );
        connect( m_tracks[i], SIGNAL( trackEndReached( unsigned int ) ), this, SLOT( trackEndReached(unsigned int) ) );
48
        connect( m_tracks[i], SIGNAL( trackPaused() ), this, SLOT( trackPaused() ) );
49
        connect( m_tracks[i], SIGNAL( trackUnpaused() ), this, SLOT( trackUnpaused() ) );
50
        connect( m_tracks[i], SIGNAL( renderCompleted( unsigned int ) ), this,  SLOT( tracksRenderCompleted( unsigned int ) ), Qt::QueuedConnection );
51
    }
52
    m_renderStartedLock = new QReadWriteLock;
53
    m_renderMutex = new QMutex;
54 55 56
    m_highestTrackNumberMutex = new QMutex;
    m_synchroneRenderWaitCondition = new QWaitCondition;
    m_synchroneRenderWaitConditionMutex = new QMutex;
57 58 59 60
}

MainWorkflow::~MainWorkflow()
{
61 62
    stop();

63 64 65 66
    delete m_synchroneRenderWaitConditionMutex;
    delete m_synchroneRenderWaitCondition;
    delete m_highestTrackNumberMutex;
    delete m_renderMutex;
67 68 69 70 71
    delete m_renderStartedLock;
    for (unsigned int i = 0; i < m_trackCount; ++i)
        delete m_tracks[i];
    delete[] m_tracks;
    delete[] blackOutput;
72 73
}

Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
74
void        MainWorkflow::addClip( Clip* clip, unsigned int trackId, qint64 start )
75
{
76
    Q_ASSERT_X( trackId < m_trackCount, "MainWorkflow::addClip",
77 78
                "The specified trackId isn't valid, for it's higher than the number of tracks");

79 80
    m_tracks[trackId]->addClip( clip, start );
    //if the track is deactivated, we need to reactivate it.
81
    if ( m_tracks[trackId].deactivated() == true )
82
        activateTrack( trackId );
83 84

    //Now check if this clip addition has changed something about the workflow's length
85 86
    if ( m_tracks[trackId]->getLength() > m_length )
        m_length = m_tracks[trackId]->getLength();
87

88
    //Inform the GUI
89
    emit clipAdded( clip, trackId, start );
90
}
91

92
void            MainWorkflow::computeLength()
93
{
94 95 96 97 98 99 100 101
    qint64      maxLength = 0;

    for ( unsigned int i = 0; i < m_trackCount; ++i )
    {
        if ( m_tracks[i]->getLength() > maxLength )
            maxLength = m_tracks[i]->getLength();
    }
    m_length = maxLength;
102 103
}

104 105 106
void    MainWorkflow::startRender()
{
    m_renderStarted = true;
107
    m_paused = false;
108 109 110
    m_currentFrame = 0;
    emit frameChanged( 0 );
    for ( unsigned int i = 0; i < m_trackCount; ++i )
111
        activateTrack( i );
112 113 114
    computeLength();
}

115
void                MainWorkflow::getOutput()
116
{
117
    QReadLocker     lock( m_renderStartedLock );
118
    QMutexLocker    lock2( m_renderMutex );
119

120 121 122 123 124 125
    {
        QMutexLocker    lockHighestTrackNumber( m_highestTrackNumberMutex );
        m_highestTrackNumber = 0;
    }
    m_nbTracksToRender = 0;
    m_synchroneRenderingBuffer = NULL;
126
    if ( m_renderStarted == true )
127
    {
128
        for ( unsigned int i = 0; i < m_trackCount; ++i )
129
        {
130 131
            if ( m_tracks[i].activated() == false )
                continue ;
132

133
            m_nbTracksToRender.fetchAndAddAcquire( 1 );
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
134
            m_tracks[i]->getOutput( m_currentFrame );
135
        }
136 137
        if ( m_paused == false )
            nextFrame();
138
    }
139
}
140

141 142
void        MainWorkflow::pause()
{
143 144
    QMutexLocker    lock( m_renderMutex );

145
    m_nbTracksToPause = 0;
146 147 148
    for ( unsigned int i = 0; i < m_trackCount; ++i )
    {
        if ( m_tracks[i].activated() == true )
149 150
        {
            m_nbTracksToPause.fetchAndAddAcquire( 1 );
151
            m_tracks[i]->pause();
152
        }
153 154 155
    }
}

156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
void        MainWorkflow::unpause()
{
    QMutexLocker    lock( m_renderMutex );

    m_nbTracksToUnpause = 0;
    for ( unsigned int i = 0; i < m_trackCount; ++i )
    {
        if ( m_tracks[i].activated() == true )
        {
            m_nbTracksToUnpause.fetchAndAddAcquire( 1 );
            m_tracks[i]->unpause();
        }
    }
}

171 172 173 174 175 176 177 178 179 180 181 182 183 184
void        MainWorkflow::nextFrame()
{
    ++m_currentFrame;
    emit frameChanged( m_currentFrame );
    emit positionChanged( (float)m_currentFrame / (float)m_length );
}

void        MainWorkflow::previousFrame()
{
    --m_currentFrame;
    emit frameChanged( m_currentFrame );
    emit positionChanged( (float)m_currentFrame / (float)m_length );
}

185 186
void        MainWorkflow::setPosition( float pos )
{
187 188
    if ( m_renderStarted == false )
        return ;
189 190 191
    //Since any track can be reactivated, we reactivate all of them, and let them
    //unable themself if required.
    for ( unsigned int i = 0; i < m_trackCount; ++i)
192
        activateTrack( i );
193

chouquette's avatar
chouquette committed
194
    qint64  frame = static_cast<qint64>( (float)m_length * pos );
195 196
    m_currentFrame = frame;
    emit frameChanged( frame );
197
//    cancelSynchronisation();
198
    //Do not emit a signal for the RenderWidget, since it's the one that triggered that call...
199 200 201 202 203 204
}

qint64      MainWorkflow::getLength() const
{
    return m_length;
}
205

206 207 208 209 210 211 212
qint64      MainWorkflow::getClipPosition( const QUuid& uuid, unsigned int trackId ) const
{
    Q_ASSERT( trackId < m_trackCount );

    return m_tracks[trackId]->getClipPosition( uuid );
}

213
void        MainWorkflow::trackEndReached( unsigned int trackId )
214
{
215 216 217 218 219 220 221
    m_tracks[trackId].deactivate();

    for ( unsigned int i = 0; i < m_trackCount; ++i)
    {
        if ( m_tracks[i].activated() == true )
            return ;
    }
222
    emit mainWorkflowEndReached();
223 224 225
    m_renderStarted = false;
    m_currentFrame = 0;
    emit frameChanged( 0 );
226
}
227 228 229 230 231

unsigned int    MainWorkflow::getTrackCount() const
{
    return m_trackCount;
}
232 233 234 235 236 237 238 239

void            MainWorkflow::stop()
{
    QWriteLocker    lock( m_renderStartedLock );

    m_renderStarted = false;
    for (unsigned int i = 0; i < m_trackCount; ++i)
    {
240 241
        if ( m_tracks[i].activated() == true )
            m_tracks[i]->stop();
242 243 244 245
    }
    m_currentFrame = 0;
    emit frameChanged( 0 );
}
246 247 248 249 250 251

MainWorkflow*   MainWorkflow::getInstance()
{
    Q_ASSERT( m_instance != NULL );
    return m_instance;
}
252

253 254 255 256 257 258 259 260 261
void            MainWorkflow::deleteInstance()
{
    if ( m_instance != NULL )
    {
        delete m_instance;
        m_instance = NULL;
    }
}

262 263
void           MainWorkflow::moveClip( const QUuid& clipUuid, unsigned int oldTrack,
                                       unsigned int newTrack, qint64 startingFrame, bool undoRedoCommand /*= false*/ )
264
{
265
    Q_ASSERT( newTrack < m_trackCount && oldTrack < m_trackCount );
266 267 268 269 270

    if ( oldTrack == newTrack )
    {
        //And now, just move the clip.
        m_tracks[newTrack]->moveClip( clipUuid, startingFrame );
271
        activateTrack( newTrack );
272 273 274 275 276
    }
    else
    {
        Clip* clip = m_tracks[oldTrack]->removeClip( clipUuid );
        m_tracks[newTrack]->addClip( clip, startingFrame );
277 278
        activateTrack( oldTrack );
        activateTrack( newTrack );
279
    }
280
    computeLength();
281 282 283 284
    if ( undoRedoCommand == true )
    {
        emit clipMoved( clipUuid, newTrack, startingFrame );
    }
285
}
286

287 288 289 290 291
Clip*       MainWorkflow::removeClip( const QUuid& uuid, unsigned int trackId )
{
    Q_ASSERT( trackId < m_trackCount );

    Clip* clip = m_tracks[trackId]->removeClip( uuid );
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
292
    activateTrack( trackId );
293 294
    emit clipRemoved( uuid, trackId );
    return clip;
295
}
296

297
void        MainWorkflow::trackPaused()
298
{
299
    m_nbTracksToPause.fetchAndAddAcquire( -1 );
300
    if ( m_nbTracksToPause <= 0 )
301
    {
302
        m_paused = true;
303
        emit mainWorkflowPaused();
304 305
    }
}
306

307
void        MainWorkflow::trackUnpaused()
308
{
309 310
    m_nbTracksToUnpause.fetchAndAddAcquire( -1 );
    if ( m_nbTracksToUnpause <= 0 )
311
    {
312
        m_paused = false;
313
        emit mainWorkflowUnpaused();
314 315
    }
}
316 317 318 319 320 321 322

void        MainWorkflow::tracksRenderCompleted( unsigned int trackId )
{
    m_nbTracksToRender.fetchAndAddAcquire( -1 );

    {
        QMutexLocker    lock( m_highestTrackNumberMutex );
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
323 324 325

        unsigned char* buff = m_tracks[trackId]->getSynchroneOutput();
        if ( m_highestTrackNumber <= trackId && buff != NULL )
326 327
        {
            m_highestTrackNumber = trackId;
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
328
            m_synchroneRenderingBuffer = buff;;
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
        }
    }
    //We check for minus or equal, since we can have 0 frame to compute,
    //therefore, m_nbTracksToRender will be equal to -1
    if ( m_nbTracksToRender <= 0 )
    {
        //Just a synchronisation barriere
        {
            QMutexLocker    lock( m_synchroneRenderWaitConditionMutex );
        }
        m_synchroneRenderWaitCondition->wakeAll();
    }
}

unsigned char*  MainWorkflow::getSynchroneOutput()
{
    m_synchroneRenderWaitConditionMutex->lock();
    getOutput();
347
//    qDebug() << "Waiting for sync output";
348
    m_synchroneRenderWaitCondition->wait( m_synchroneRenderWaitConditionMutex );
349
//    qDebug() << "Got it";
350 351 352 353 354
    m_synchroneRenderWaitConditionMutex->unlock();
    if ( m_synchroneRenderingBuffer == NULL )
        return MainWorkflow::blackOutput;
    return m_synchroneRenderingBuffer;
}
355 356 357 358 359 360 361 362

void        MainWorkflow::cancelSynchronisation()
{
    {
        QMutexLocker    lock( m_synchroneRenderWaitConditionMutex );
    }
    m_synchroneRenderWaitCondition->wakeAll();
}
363 364 365 366 367 368 369 370 371 372

void        MainWorkflow::muteTrack( unsigned int trackId )
{
    m_tracks[trackId].setHardDeactivation( true );
}

void        MainWorkflow::unmuteTrack( unsigned int trackId )
{
    m_tracks[trackId].setHardDeactivation( false );
}
373 374 375 376 377 378

void        MainWorkflow::setCurrentFrame( qint64 currentFrame )
{
    m_currentFrame = currentFrame;
    emit positionChanged( (float)m_currentFrame / (float)m_length );
}
379

Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
380 381 382 383 384 385 386
void        MainWorkflow::activateTrack( unsigned int trackId )
{
    if ( m_tracks[trackId]->getLength() > 0 )
        m_tracks[trackId].activate();
    else
        m_tracks[trackId].deactivate();
}
387 388 389 390 391 392 393

Clip*       MainWorkflow::getClip( const QUuid& uuid, unsigned int trackId )
{
    Q_ASSERT( trackId < m_trackCount );

    return m_tracks[trackId]->getClip( uuid );
}
394 395 396

void        MainWorkflow::loadProject( const QDomElement& project )
{
397 398 399 400 401 402
    if ( project.isNull() == true || project.tagName() != "timeline" )
    {
        qWarning() << "Invalid timeline node (" << project.tagName() << ')';
        return ;
    }

403 404
    clear();

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
    QDomElement elem = project.firstChild().toElement();

    while ( elem.isNull() == false )
    {
        bool    ok;

        Q_ASSERT( elem.tagName() == "track" );
        unsigned int trackId = elem.attribute( "id" ).toUInt( &ok );
        if ( ok == false )
        {
            qWarning() << "Invalid track number in project file";
            return ;
        }
        QDomElement clip = elem.firstChild().toElement();
        while ( clip.isNull() == false )
        {
            //Iterate over clip fields:
            QDomElement clipProperty = clip.firstChild().toElement();
            QUuid       parent;
            float       begin;
            float       end;
            qint64      startPos;

            while ( clipProperty.isNull() == false )
            {
                QString tagName = clipProperty.tagName();
                bool    ok;

                if ( tagName == "parent" )
                    parent = QUuid( clipProperty.text() );
                else if ( tagName == "begin" )
                {
                    begin = clipProperty.text().toFloat( &ok );
                    if ( ok == false )
                    {
                        qWarning() << "Invalid clip begin";
                        return ;
                    }
                }
                else if ( tagName == "end" )
                {
                    end = clipProperty.text().toFloat( &ok );
                    if ( ok == false )
                    {
                        qWarning() << "Invalid clip end";
                        return ;
                    }
                }
                else if ( tagName == "startFrame" )
                {
                    startPos = clipProperty.text().toLongLong( &ok );
                    if ( ok == false )
                    {
                        qWarning() << "Invalid clip starting frame";
                        return ;
                    }
                }
                else
                    qDebug() << "Unknown field" << clipProperty.tagName();

                clipProperty = clipProperty.nextSibling().toElement();
            }

            Clip*       c = new Clip( parent, begin, end );
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
469
            addClip( c, trackId, startPos );
470 471 472 473 474

            clip = clip.nextSibling().toElement();
        }
        elem = elem.nextSibling().toElement();
    }
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
}

void        MainWorkflow::saveProject( QDomDocument& doc )
{
    QDomElement project = doc.createElement( "timeline" );
    for ( unsigned int i = 0; i < m_trackCount; ++i )
    {
        if ( m_tracks[i]->getLength() > 0 )
        {
            QDomElement     trackNode = doc.createElement( "track" );

            trackNode.setAttribute( "id", i );

            m_tracks[i]->save( doc, trackNode );
            project.appendChild( trackNode );
        }
    }
    doc.appendChild( project );
}
494 495 496 497 498 499 500 501

void        MainWorkflow::clear()
{
    for ( unsigned int i = 0; i < m_trackCount; ++i )
    {
        m_tracks[i]->clear();
    }
    m_length = 0;
502
    emit cleared();
503
}