TrackWorkflow.cpp 15.1 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 <QtDebug>

25
#include "vlmc.h"
26
#include "TrackWorkflow.h"
27
#include "VideoClipWorkflow.h"
28
#include "ImageClipWorkflow.h"
29
#include "AudioClipWorkflow.h"
30 31
#include "Clip.h"
#include "Media.h"
32

33 34 35
#include <QReadWriteLock>
#include <QDomDocument>
#include <QDomElement>
36
#include <QXmlStreamWriter>
37

38
TrackWorkflow::TrackWorkflow( unsigned int trackId, MainWorkflow::TrackType type  ) :
39
        m_trackId( trackId ),
40
        m_length( 0 ),
41
        m_trackType( type ),
42 43 44
        m_lastFrame( 0 ),
        m_videoStackedBuffer( NULL ),
        m_audioStackedBuffer( NULL )
45
{
46
    m_renderOneFrameMutex = new QMutex;
47
    m_clipsLock = new QReadWriteLock;
48 49
}

50 51
TrackWorkflow::~TrackWorkflow()
{
52 53
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();
54 55 56 57 58 59 60

    while ( it != end )
    {
        stopClipWorkflow( it.value() );
        delete it.value();
        it = m_clips.erase( it );
    }
61
    delete m_clipsLock;
62
    delete m_renderOneFrameMutex;
63 64
}

65 66
const QUuid&
TrackWorkflow::addClip( Clip* clip, qint64 start )
67
{
68
    ClipWorkflow* cw;
69
    if ( m_trackType == MainWorkflow::VideoTrack )
70
    {
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
71
        if ( clip->getMedia()->fileType() == Media::Video )
72 73 74 75
            cw = new VideoClipWorkflow( clip );
        else
            cw = new ImageClipWorkflow( clip );
    }
76 77
    else
        cw = new AudioClipWorkflow( clip );
78
    addClip( cw, start );
79
    return cw->uuid();
80 81
}

82 83
void
TrackWorkflow::addClip( ClipWorkflow* cw, qint64 start )
84 85 86 87 88 89
{
    QWriteLocker    lock( m_clipsLock );
    m_clips.insert( start, cw );
    computeLength();
}

90
//Must be called from a thread safe method (m_clipsLock locked)
91 92
void
TrackWorkflow::computeLength()
93 94
{
    if ( m_clips.count() == 0 )
95
    {
96
        m_length = 0;
97 98
        return ;
    }
99
    QMap<qint64, ClipWorkflow*>::const_iterator it = m_clips.end() - 1;
100
    m_length = (it.key() + it.value()->getClip()->length() );
101 102 103 104 105 106 107
}

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

108
qint64              TrackWorkflow::getClipPosition( const QUuid& uuid ) const
109
{
110 111
    QMap<qint64, ClipWorkflow*>::const_iterator     it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::const_iterator     end = m_clips.end();
112 113 114

    while ( it != end )
    {
115
        if ( it.value()->uuid() == uuid )
116 117 118 119
            return it.key();
        ++it;
    }
    return -1;
120 121
}

122 123 124 125 126 127 128
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 )
    {
129
        if ( it.value()->uuid() == uuid )
130 131 132 133 134 135
            return it.value()->getClip();
        ++it;
    }
    return NULL;
}

136 137
void*
TrackWorkflow::renderClip( ClipWorkflow* cw, qint64 currentFrame,
138
                                        qint64 start , bool needRepositioning,
139
                                        bool renderOneFrame, bool paused )
140
{
141
    ClipWorkflow::GetMode       mode = ( paused == false || renderOneFrame == true ?
142
                                         ClipWorkflow::Pop : ClipWorkflow::Get );
143

144
    cw->getStateLock()->lockForRead();
145 146 147 148
    if ( cw->getState() == ClipWorkflow::Rendering ||
         cw->getState() == ClipWorkflow::Paused ||
         cw->getState() == ClipWorkflow::PauseRequired ||
         cw->getState() == ClipWorkflow::UnpauseRequired )
149
    {
150
        cw->getStateLock()->unlock();
151 152

        if ( cw->isResyncRequired() == true || needRepositioning == true )
153
            adjustClipTime( currentFrame, start, cw );
154
        return cw->getOutput( mode );
155
    }
156
    else if ( cw->getState() == ClipWorkflow::Stopped )
157
    {
158
        cw->getStateLock()->unlock();
159
        cw->initialize();
160
        cw->waitForCompleteInit();
161
        //We check for a difference greater than one to avoid false positive when starting.
162
        if ( (  qAbs(start - currentFrame) > 1 ) || cw->getClip()->begin() != 0 )
163
        {
164
            //Clip was not started as its real begining: adjust the position
165
            adjustClipTime( currentFrame, start, cw );
166
        }
167
        return cw->getOutput( mode );
168
    }
169 170
    else if ( cw->getState() == ClipWorkflow::EndReached ||
              cw->getState() == ClipWorkflow::Muted )
171 172 173 174
    {
        cw->getStateLock()->unlock();
        //The stopClipWorkflow() method will take care of that.
    }
175
    else
176
    {
177
        qCritical() << "Unexpected state:" << cw->getState();
178
        cw->getStateLock()->unlock();
179
    }
180
    return NULL;
181
}
182

183
void                TrackWorkflow::preloadClip( ClipWorkflow* cw )
184
{
185
    cw->getStateLock()->lockForRead();
186

187
    if ( cw->getState() == ClipWorkflow::Stopped )
188
    {
189
        cw->getStateLock()->unlock();
190
        cw->initialize();
191
        return ;
192
    }
193
    cw->getStateLock()->unlock();
194 195
}

196
void                TrackWorkflow::stopClipWorkflow( ClipWorkflow* cw )
197
{
198
//    qDebug() << "Stopping clip workflow";
199
    cw->getStateLock()->lockForRead();
200

201 202
    if ( cw->getState() == ClipWorkflow::Stopped ||
         cw->getState() == ClipWorkflow::Muted )
203
    {
204 205
        cw->getStateLock()->unlock();
        return ;
206
    }
207 208
    cw->getStateLock()->unlock();
    cw->stop();
209 210
}

211 212 213 214 215 216 217
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.
218
    return ( it.value()->getClip()->length() + it.key() < currentFrame );
219 220
}

221 222
void
TrackWorkflow::stop()
223
{
224 225
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();
226 227 228 229 230 231

    while ( it != end )
    {
        stopClipWorkflow( it.value() );
        ++it;
    }
232
    releasePreviousRender();
233
    m_lastFrame = 0;
234 235
}

236 237
void
TrackWorkflow::releasePreviousRender()
238 239
{
    if ( m_audioStackedBuffer != NULL )
240
    {
241
        m_audioStackedBuffer->release();
242 243
        m_audioStackedBuffer = NULL;
    }
244
    if ( m_videoStackedBuffer != NULL )
245
    {
246
        m_videoStackedBuffer->release();
247 248
        m_videoStackedBuffer = NULL;
    }
249 250
}

251
void*
252
TrackWorkflow::getOutput( qint64 currentFrame, qint64 subFrame, bool paused )
253
{
254
    releasePreviousRender();
255 256
    QReadLocker     lock( m_clipsLock );

257 258 259
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();
    bool                                        needRepositioning;
260
    void*                                       ret = NULL;
261
    bool                                        renderOneFrame = false;
262

263 264
    if ( m_lastFrame == -1 )
        m_lastFrame = currentFrame;
265 266 267
    if ( checkEnd( currentFrame ) == true )
    {
        emit trackEndReached( m_trackId );
268
        //We continue, as there can be ClipWorkflow that requires to be stopped.
269
    }
270 271 272 273 274 275 276 277
    {
        QMutexLocker      lock2( m_renderOneFrameMutex );
        if ( m_renderOneFrame == true )
        {
            m_renderOneFrame = false;
            renderOneFrame = true;
        }
    }
278
    {
279 280 281 282 283 284
        // 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
        // false. Easy ain't it ?
285
        if ( paused == true && subFrame != m_lastFrame && renderOneFrame == false)
286
            needRepositioning = true;
287
        else
288
            needRepositioning = ( abs( subFrame - m_lastFrame ) > 1 ) ? true : false;
289
    }
290
    while ( it != end )
291
    {
292 293 294
        qint64          start = it.key();
        ClipWorkflow*   cw = it.value();
        //Is the clip supposed to render now ?
295
        if ( start <= currentFrame && currentFrame <= start + cw->getClip()->length() )
296
        {
297 298
            if ( ret != NULL )
                qCritical() << "There's more than one clip to render here. Undefined behaviour !";
299 300
            ret = renderClip( cw, currentFrame, start, needRepositioning,
                              renderOneFrame, paused );
301
            if ( m_trackType == MainWorkflow::VideoTrack )
302
                m_videoStackedBuffer = reinterpret_cast<StackedBuffer<LightVideoFrame*>*>( ret );
303
            else
304
                m_audioStackedBuffer = reinterpret_cast<StackedBuffer<AudioClipWorkflow::AudioSample*>*>( ret );
305
        }
306 307 308 309 310 311
        //Is it about to be rendered ?
        else if ( start > currentFrame &&
                start - currentFrame < TrackWorkflow::nbFrameBeforePreload )
            preloadClip( cw );
        //Is it supposed to be stopped ?
        else
312 313
            stopClipWorkflow( cw );
        ++it;
314
    }
315
    m_lastFrame = subFrame;
316

317
    return ret;
318 319
}

320
void            TrackWorkflow::moveClip( const QUuid& id, qint64 startingFrame )
321
{
322 323
    QWriteLocker    lock( m_clipsLock );

324 325
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();
326 327 328

    while ( it != end )
    {
329
        if ( it.value()->uuid() == id )
330 331 332 333
        {
            ClipWorkflow* cw = it.value();
            m_clips.erase( it );
            m_clips[startingFrame] = cw;
334
            cw->requireResync();
335
            computeLength();
336 337 338 339 340 341 342
            return ;
        }
        ++it;
    }
    qDebug() << "Track" << m_trackId << "was asked to move clip" << id << "to position" << startingFrame
            << "but this clip doesn't exist in this track";
}
343 344 345 346 347

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

348 349
    QMap<qint64, ClipWorkflow*>::iterator       it = m_clips.begin();
    QMap<qint64, ClipWorkflow*>::iterator       end = m_clips.end();
350 351 352

    while ( it != end )
    {
353
        if ( it.value()->uuid() == id )
354 355
        {
            ClipWorkflow*   cw = it.value();
356
            Clip*           clip = cw->getClip();
357
            m_clips.erase( it );
358
            stopClipWorkflow( cw );
359
            computeLength();
360
            cw->disconnect();
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
361
            delete cw;
362
            qDebug() << "Deleted clipworkflow";
363 364
            if ( m_length == 0 )
                emit trackEndReached( m_trackId );
365
            return clip;
366 367 368 369 370
        }
        ++it;
    }
    return NULL;
}
371

372 373 374 375 376 377 378 379 380
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 )
    {
381
        if ( it.value()->uuid() == id )
382 383
        {
            ClipWorkflow*   cw = it.value();
384
            cw->disconnect();
385 386 387 388 389 390 391 392 393 394
            m_clips.erase( it );
            computeLength();
            return cw;

        }
        ++it;
    }
    return NULL;
}

395
void    TrackWorkflow::save( QXmlStreamWriter& project ) const
396 397 398 399 400 401 402 403
{
    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 )
    {
404
        project.writeStartElement( "clip" );
405
        project.writeAttribute( "uuid", it.value()->getClip()->getParent()->fullId() );
406
        project.writeAttribute( "startFrame", QString::number( it.key() ) );
407 408
        project.writeAttribute( "begin", QString::number( it.value()->getClip()->begin() ) );
        project.writeAttribute( "end", QString::number( it.value()->getClip()->end() ) );
409
        project.writeEndElement();
410 411 412
    }
}

413 414 415 416 417 418 419 420 421
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();
422
        //The clip contained in the trackworkflow will be delete by the undo stack.
423 424 425 426 427
        delete cw;
    }
    m_clips.clear();
    m_length = 0;
}
428 429 430

void    TrackWorkflow::adjustClipTime( qint64 currentFrame, qint64 start, ClipWorkflow* cw )
{
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
431 432
    qint64  nbMs = ( currentFrame - start ) / cw->getClip()->getMedia()->fps() * 1000;
    qint64  beginInMs = cw->getClip()->begin() / cw->getClip()->getMedia()->fps() * 1000;
433
    qint64  startFrame = beginInMs + nbMs;
434 435
    cw->setTime( startFrame );
}
436

437 438 439 440 441 442
void
TrackWorkflow::renderOneFrame()
{
    QMutexLocker    lock( m_renderOneFrameMutex );
    m_renderOneFrame = true;
}
443 444 445 446 447 448 449 450 451

void
TrackWorkflow::setFullSpeedRender( bool val )
{
    foreach ( ClipWorkflow* cw, m_clips.values() )
    {
        cw->setFullSpeedRender( val );
    }
}
452 453 454 455 456 457 458 459 460 461 462

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 )
    {
463
        if ( it.value()->uuid() == uuid )
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
        {
            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 )
    {
484
        if ( it.value()->uuid() == uuid )
485 486 487 488 489 490 491 492 493
        {
            it.value()->unmute();
            return ;
        }
        ++it;
    }
    qWarning() << "Failed to unmute clip" << uuid << "it probably doesn't exist "
            "in this track";
}
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510

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

    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;
    }
}