TrackWorkflow.cpp 18.2 KB
Newer Older
1 2 3
/*****************************************************************************
 * TrackWorkflow.cpp : Will query the Clip workflow for each successive clip in the track
 *****************************************************************************
Ludovic Fauvet's avatar
Ludovic Fauvet committed
4
 * Copyright (C) 2008-2010 VideoLAN
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 *
 * 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 "TrackWorkflow.h"
25

26
#include "Clip.h"
27
#include "ClipHelper.h"
28
#include "AudioClipWorkflow.h"
29
#include "ImageClipWorkflow.h"
30
#include "MainWorkflow.h"
31
#include "Media.h"
32
#include "EffectInstance.h"
33
#include "Types.h"
34 35
#include "VideoClipWorkflow.h"
#include "vlmc.h"
36

37 38
#include <QDomDocument>
#include <QDomElement>
39 40
#include <QMutex>
#include <QReadWriteLock>
41

42 43
#include <QtDebug>

44
TrackWorkflow::TrackWorkflow( Workflow::TrackType type, quint32 trackId  ) :
45
        m_length( 0 ),
46
        m_trackType( type ),
47
        m_lastFrame( 0 ),
48
        m_trackId( trackId )
49
{
50
    m_renderOneFrameMutex = new QMutex;
51
    m_clipsLock = new QReadWriteLock;
52
    m_mixerBuffer = new Workflow::Frame;
53 54
}

55 56
TrackWorkflow::~TrackWorkflow()
{
57 58
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();
59 60 61 62 63 64 65

    while ( it != end )
    {
        stopClipWorkflow( it.value() );
        delete it.value();
        it = m_clips.erase( it );
    }
66
    delete m_clipsLock;
67
    delete m_renderOneFrameMutex;
68 69
}

70 71
void
TrackWorkflow::addClip( ClipHelper* ch, qint64 start )
72
{
73
    ClipWorkflow* cw;
74
    if ( m_trackType == Workflow::VideoTrack )
75
    {
76 77
        if ( ch->clip()->getMedia()->fileType() == Media::Video )
            cw = new VideoClipWorkflow( ch );
78
        else
79
            cw = new ImageClipWorkflow( ch );
80
    }
81
    else
82
        cw = new AudioClipWorkflow( ch );
83
    ch->setClipWorkflow( cw );
84
    addClip( cw, start );
85 86
}

87 88
void
TrackWorkflow::addClip( ClipWorkflow* cw, qint64 start )
89 90 91
{
    QWriteLocker    lock( m_clipsLock );
    m_clips.insert( start, cw );
92
    emit clipAdded( this, cw->getClipHelper(), start );
93 94 95
    computeLength();
}

96
//Must be called from a thread safe method (m_clipsLock locked)
97 98
void
TrackWorkflow::computeLength()
99
{
100
    bool    changed = false;
101
    if ( m_clips.count() == 0 )
102
    {
103 104
        if ( m_length != 0 )
            changed = true;
105
        m_length = 0;
106
    }
107 108 109 110 111 112 113 114 115 116
    else
    {
        QMap<qint64, ClipWorkflow*>::const_iterator it = m_clips.end() - 1;
        qint64  newLength = it.key() + it.value()->getClipHelper()->length();
        if ( m_length != newLength )
            changed = true;
        m_length = newLength;
    }
    if ( changed == true )
        emit lengthChanged( m_length );
117 118
}

119 120
qint64
TrackWorkflow::getLength() const
121 122 123 124
{
    return m_length;
}

125 126
qint64
TrackWorkflow::getClipPosition( const QUuid& uuid ) const
127
{
128 129
    QMap<qint64, ClipWorkflow*>::const_iterator     it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::const_iterator     end = m_clips.end();
130 131 132

    while ( it != end )
    {
133
        if ( it.value()->getClipHelper()->uuid() == uuid )
134 135 136 137
            return it.key();
        ++it;
    }
    return -1;
138 139
}

140 141
ClipHelper*
TrackWorkflow::getClipHelper( const QUuid& uuid )
142 143 144 145 146 147
{
    QMap<qint64, ClipWorkflow*>::const_iterator     it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::const_iterator     end = m_clips.end();

    while ( it != end )
    {
148 149
        if ( it.value()->getClipHelper()->uuid() == uuid )
            return it.value()->getClipHelper();
150 151 152 153 154
        ++it;
    }
    return NULL;
}

155
Workflow::OutputBuffer*
156
TrackWorkflow::renderClip( ClipWorkflow* cw, qint64 currentFrame,
157
                                        qint64 start , bool needRepositioning,
158
                                        bool renderOneFrame, bool paused )
159
{
160
    ClipWorkflow::GetMode       mode = ( paused == false || renderOneFrame == true ?
161
                                         ClipWorkflow::Pop : ClipWorkflow::Get );
162

163
    cw->getStateLock()->lockForRead();
164 165 166 167
    if ( cw->getState() == ClipWorkflow::Rendering ||
         cw->getState() == ClipWorkflow::Paused ||
         cw->getState() == ClipWorkflow::PauseRequired ||
         cw->getState() == ClipWorkflow::UnpauseRequired )
168
    {
169
        cw->getStateLock()->unlock();
170 171

        if ( cw->isResyncRequired() == true || needRepositioning == true )
172
            adjustClipTime( currentFrame, start, cw );
173
        return cw->getOutput( mode );
174
    }
175
    else if ( cw->getState() == ClipWorkflow::Stopped )
176
    {
177
        cw->getStateLock()->unlock();
178
        cw->initialize();
179 180 181
        //If the init failed, don't even try to call getOutput.
        if ( cw->waitForCompleteInit() == false )
            return NULL;
182
        //We check for a difference greater than one to avoid false positive when starting.
183
        if ( (  qAbs(start - currentFrame) > 1 ) || cw->getClipHelper()->begin() != 0 )
184
        {
185
            //Clip was not started at its real begining: adjust the position
186
            adjustClipTime( currentFrame, start, cw );
187
        }
188
        return cw->getOutput( mode );
189
    }
190
    else if ( cw->getState() == ClipWorkflow::EndReached ||
191 192
              cw->getState() == ClipWorkflow::Muted ||
              cw->getState() == ClipWorkflow::Error )
193 194 195 196
    {
        cw->getStateLock()->unlock();
        //The stopClipWorkflow() method will take care of that.
    }
197
    else
198
    {
199
        qCritical() << "Unexpected state:" << cw->getState();
200
        cw->getStateLock()->unlock();
201
    }
202
    return NULL;
203
}
204

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

210
    if ( cw->getState() == ClipWorkflow::Stopped )
211
    {
212
        cw->getStateLock()->unlock();
213
        cw->initialize();
214
        return ;
215
    }
216
    cw->getStateLock()->unlock();
217 218
}

219 220
void
TrackWorkflow::stopClipWorkflow( ClipWorkflow* cw )
221
{
222
//    qDebug() << "Stopping clip workflow";
223
    cw->getStateLock()->lockForRead();
224

225
    if ( cw->getState() == ClipWorkflow::Stopped ||
226 227
         cw->getState() == ClipWorkflow::Muted ||
         cw->getState() == ClipWorkflow::Error )
228
    {
229 230
        cw->getStateLock()->unlock();
        return ;
231
    }
232 233
    cw->getStateLock()->unlock();
    cw->stop();
234 235
}

236
bool
237
TrackWorkflow::hasNoMoreFrameToRender( qint64 currentFrame ) const
238 239 240 241 242
{
    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;
243 244 245 246 247 248 249
    ClipWorkflow* cw = it.value();
    //Check if the Clip is in error state. If so, don't bother checking anything else.
    {
        QReadLocker     lock( cw->getStateLock() );
        if ( cw->getState() == ClipWorkflow::Error )
            return true;
    }
250
    //If it ends before the current frame, we reached end.
251
    return ( cw->getClipHelper()->length() + it.key() < currentFrame );
252 253
}

254 255
void
TrackWorkflow::stop()
256
{
257 258
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();
259 260 261 262 263 264

    while ( it != end )
    {
        stopClipWorkflow( it.value() );
        ++it;
    }
265
    m_lastFrame = 0;
266
    m_isRendering = false;
267 268
}

269
Workflow::OutputBuffer*
270
TrackWorkflow::getOutput( qint64 currentFrame, qint64 subFrame, bool paused )
271
{
272 273
    QReadLocker     lock( m_clipsLock );

274 275 276
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();
    bool                                        needRepositioning;
277
    Workflow::OutputBuffer                      *ret = NULL;
278
    Workflow::Frame                             *frames[EffectsEngine::MaxFramesForMixer];
279
    quint32                                     frameId = 0;
280
    bool                                        renderOneFrame = false;
281

282 283
    if ( m_lastFrame == -1 )
        m_lastFrame = currentFrame;
284 285 286 287 288 289 290 291
    {
        QMutexLocker      lock2( m_renderOneFrameMutex );
        if ( m_renderOneFrame == true )
        {
            m_renderOneFrame = false;
            renderOneFrame = true;
        }
    }
292
    {
293 294 295 296 297
        // This is a bit hackish : when we want to pop a frame in renderOneFrame mode,
        // we also set the position to avoid the stream to be missynchronized.
        // this frame setting will most likely toggle the next condition as true
        // If this condition is true, the clipworkflow will flush all its buffer
        // as we need to resynchronize after a setTime, so this condition has to remain
Nikoli's avatar
Nikoli committed
298
        // false. Easy ain't it?
299
        if ( paused == true && subFrame != m_lastFrame && renderOneFrame == false)
300
            needRepositioning = true;
301
        else
302
            needRepositioning = ( abs( subFrame - m_lastFrame ) > 1 ) ? true : false;
303
    }
304
    memset( frames, 0, sizeof(*frames) * EffectsEngine::MaxFramesForMixer );
305
    while ( it != end )
306
    {
307 308
        qint64          start = it.key();
        ClipWorkflow*   cw = it.value();
Nikoli's avatar
Nikoli committed
309
        //Is the clip supposed to render now?
310
        if ( start <= currentFrame && currentFrame <= start + cw->getClipHelper()->length() )
311
        {
312 313
            ret = renderClip( cw, currentFrame, start, needRepositioning,
                              renderOneFrame, paused );
314
            if ( m_trackType == Workflow::VideoTrack )
315
            {
316
                frames[frameId] = static_cast<Workflow::Frame*>( ret );
317 318
                ++frameId;
            }
319
        }
Nikoli's avatar
Nikoli committed
320
        //Is it about to be rendered?
321 322 323
        else if ( start > currentFrame &&
                start - currentFrame < TrackWorkflow::nbFrameBeforePreload )
            preloadClip( cw );
Nikoli's avatar
Nikoli committed
324
        //Is it supposed to be stopped?
325
        else
326 327
            stopClipWorkflow( cw );
        ++it;
328
    }
329
    //Handle mixers:
330
    if ( m_trackType == Workflow::VideoTrack )
331
    {
332
        EffectsEngine::EffectHelper* mixer = getMixer( currentFrame );
333
        if ( mixer != NULL && frames[0] != NULL ) //There's no point using the mixer if there's no frame rendered.
334
        {
335 336
            //FIXME: We don't handle mixer3 yet.
            mixer->effect->process( currentFrame * 1000.0 / m_fps,
337
                                    frames[0]->buffer(),
338
                                    frames[1] != NULL ? frames[1]->buffer() : MainWorkflow::getInstance()->blackOutput()->buffer(),
339
                                    NULL, m_mixerBuffer->buffer() );
340
            m_mixerBuffer->ptsDiff = frames[0]->ptsDiff;
341
            ret = m_mixerBuffer;
342
        }
343
        else //If there's no mixer, just use the first frame, ignore the rest. It will be cleaned by the responsible ClipWorkflow.
344
            ret = frames[0];
345
        //Now handle filters :
346 347
        quint32     *newFrame = applyFilters( ret != NULL ? static_cast<const Workflow::Frame*>( ret ) : MainWorkflow::getInstance()->blackOutput(),
                                                currentFrame, currentFrame * 1000.0 / m_fps );
348
        if ( newFrame != NULL )
349 350 351 352 353 354 355 356 357
        {
            if ( ret != NULL )
                static_cast<Workflow::Frame*>( ret )->setBuffer( newFrame );
            else //Use the m_mixerBuffer as the frame to return. Ugly but avoid another attribute.
            {
                m_mixerBuffer->setBuffer( newFrame );
                ret = m_mixerBuffer;
            }
        }
358
    }
359
    m_lastFrame = subFrame;
360
    return ret;
361 362
}

363
void            TrackWorkflow::moveClip( const QUuid& id, qint64 startingFrame )
364
{
365 366
    QWriteLocker    lock( m_clipsLock );

367 368
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();
369 370 371

    while ( it != end )
    {
372
        if ( it.value()->getClipHelper()->uuid() == id )
373 374 375 376
        {
            ClipWorkflow* cw = it.value();
            m_clips.erase( it );
            m_clips[startingFrame] = cw;
377
            cw->requireResync();
378
            computeLength();
379
            emit clipMoved( this, cw->getClipHelper(), startingFrame );
380 381 382 383 384
            return ;
        }
        ++it;
    }
}
385 386 387 388 389

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

390 391
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();
392 393 394

    while ( it != end )
    {
395
        if ( it.value()->getClipHelper()->uuid() == id )
396 397
        {
            ClipWorkflow*   cw = it.value();
398
            Clip*           clip = cw->clip();
399
            m_clips.erase( it );
400
            stopClipWorkflow( cw );
401
            computeLength();
402
            cw->disconnect();
403
            emit clipRemoved( this, cw->getClipHelper() );
404
            delete cw;
405
            return clip;
406 407 408 409 410
        }
        ++it;
    }
    return NULL;
}
411

412 413 414 415 416 417 418 419 420
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 )
    {
421
        if ( it.value()->getClipHelper()->uuid() == id )
422 423
        {
            ClipWorkflow*   cw = it.value();
424
            cw->disconnect();
425 426 427 428 429 430 431 432 433 434
            m_clips.erase( it );
            computeLength();
            return cw;

        }
        ++it;
    }
    return NULL;
}

435
void    TrackWorkflow::save( QXmlStreamWriter& project ) const
436 437 438 439 440 441 442 443
{
    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 )
    {
444 445
        project.writeStartElement( "clip" );
        project.writeAttribute( "startFrame", QString::number( it.key() ) );
446
        it.value()->save( project );
447
        project.writeEndElement();
448 449 450
    }
}

451 452 453 454 455 456 457 458 459
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();
460
        //The clip contained in the trackworkflow will be delete by the undo stack.
461 462 463 464 465
        delete cw;
    }
    m_clips.clear();
    m_length = 0;
}
466 467 468

void    TrackWorkflow::adjustClipTime( qint64 currentFrame, qint64 start, ClipWorkflow* cw )
{
469 470
    qint64  nbMs = ( currentFrame - start ) / cw->clip()->getMedia()->fps() * 1000;
    qint64  beginInMs = cw->getClipHelper()->begin() / cw->clip()->getMedia()->fps() * 1000;
471
    qint64  startFrame = beginInMs + nbMs;
472
    cw->setTime( startFrame, currentFrame );
473
}
474

475 476 477 478 479 480
void
TrackWorkflow::renderOneFrame()
{
    QMutexLocker    lock( m_renderOneFrameMutex );
    m_renderOneFrame = true;
}
481 482 483 484 485 486 487 488 489

void
TrackWorkflow::setFullSpeedRender( bool val )
{
    foreach ( ClipWorkflow* cw, m_clips.values() )
    {
        cw->setFullSpeedRender( val );
    }
}
490 491 492 493 494 495 496 497 498 499 500

void
TrackWorkflow::muteClip( const QUuid &uuid )
{
    QWriteLocker    lock( m_clipsLock );

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

    while ( it != end )
    {
501
        if ( it.value()->getClipHelper()->uuid() == uuid )
502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
        {
            it.value()->mute();
            return ;
        }
        ++it;
    }
    qWarning() << "Failed to mute clip" << uuid << "it probably doesn't exist "
            "in this track";
}

void
TrackWorkflow::unmuteClip( const QUuid &uuid )
{
    QWriteLocker    lock( m_clipsLock );

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

    while ( it != end )
    {
522
        if ( it.value()->getClipHelper()->uuid() == uuid )
523 524 525 526 527 528 529 530 531
        {
            it.value()->unmute();
            return ;
        }
        ++it;
    }
    qWarning() << "Failed to unmute clip" << uuid << "it probably doesn't exist "
            "in this track";
}
532 533

void
534
TrackWorkflow::initRender( quint32 width, quint32 height, double fps )
535 536 537
{
    QReadLocker     lock( m_clipsLock );

538
    m_mixerBuffer->resize( width, height );
539
    m_fps = fps;
540 541 542
    m_width = width;
    m_height = height;
    m_isRendering = true;
543 544 545 546 547 548 549 550 551 552
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();
    while ( it != end )
    {
        qint64          start = it.key();
        ClipWorkflow*   cw = it.value();
        if ( start < TrackWorkflow::nbFrameBeforePreload )
            preloadClip( cw );
        ++it;
    }
553 554
    initFilters();
    initMixers();
555
}
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571

bool
TrackWorkflow::contains( const QUuid &uuid ) const
{
    QMap<qint64, ClipWorkflow*>::const_iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::const_iterator       end = m_clips.end();

    while ( it != end )
    {
        if ( it.value()->getClipHelper()->clip()->uuid() == uuid ||
             it.value()->getClipHelper()->clip()->isChild( uuid ) )
            return true;
        ++it;
    }
    return false;
}
572 573 574 575 576 577 578 579 580 581 582 583 584 585

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

    while ( it != end )
    {
        ClipWorkflow*   cw = it.value();

        cw->getStateLock()->lockForRead();

        if ( cw->getState() == ClipWorkflow::Stopped ||
586 587
             cw->getState() == ClipWorkflow::Muted ||
             cw->getState() == ClipWorkflow::Error )
588 589 590 591 592 593 594 595 596
        {
            cw->getStateLock()->unlock();
            return ;
        }
        cw->getStateLock()->unlock();
        cw->stopRenderer();
        ++it;
    }
}
597 598 599 600 601 602 603 604 605 606 607 608

quint32
TrackWorkflow::trackId() const
{
    return m_trackId;
}

Workflow::TrackType
TrackWorkflow::type() const
{
    return m_trackType;
}
609 610 611 612 613 614 615 616 617 618 619 620

EffectsEngine::EffectList*
TrackWorkflow::filters()
{
    return &m_filters;
}

EffectsEngine::EffectList*
TrackWorkflow::mixers()
{
    return &m_mixers;
}