WorkflowRenderer.cpp 14.5 KB
Newer Older
1
/*****************************************************************************
2
 * WorkflowRenderer.cpp: Allow a current workflow preview
3
 *****************************************************************************
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
 *
 * 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.
 *****************************************************************************/

23
//Allow PRId64 to be defined:
24
#define __STDC_FORMAT_MACROS
25 26

#include "WorkflowRenderer.h"
27

28
#include "Clip.h"
29
#include "FilterInstance.h"
30 31 32 33
#include "GenericRenderer.h"
#include "MainWorkflow.h"
#include "SettingsManager.h"
#include "VLCMedia.h"
34
#include "VLCMediaPlayer.h"
35 36
#include "Workflow/Types.h"
#include "timeline/Timeline.h"
37

38 39 40 41 42 43
#include <QDomElement>
#include <QtDebug>
#include <QThread>
#include <QWaitCondition>
#include <inttypes.h>

44 45
WorkflowRenderer::WorkflowRenderer() :
            m_mainWorkflow( MainWorkflow::getInstance() ),
46
            m_media( NULL ),
47
            m_stopping( false ),
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
48
            m_outputFps( 0.0f ),
49
            m_width( 0 ),
50
            m_height( 0 ),
51
            m_silencedAudioBuffer( NULL ),
52
            m_esHandler( NULL ),
53
            m_oldLength( 0 )
54
{
55
    m_effectsLock = new QReadWriteLock;
56 57 58
}

void    WorkflowRenderer::initializeRenderer()
59
{
60 61
    m_esHandler = new EsHandler;
    m_esHandler->self = this;
62

63
    m_nbChannels = 2;
64
    m_rate = 48000;
65

Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
66
     //Workflow part
67
    connect( m_mainWorkflow, SIGNAL( mainWorkflowEndReached() ), this, SLOT( __endReached() ), Qt::QueuedConnection );
68
    connect( m_mainWorkflow, SIGNAL( frameChanged( qint64, MainWorkflow::FrameChangedReason ) ),
69
             this, SIGNAL( frameChanged( qint64, MainWorkflow::FrameChangedReason ) ) );
70 71
    connect( m_mainWorkflow, SIGNAL( lengthChanged( qint64 ) ),
             this, SLOT(mainWorkflowLenghtChanged(qint64) ) );
72 73 74
    //Media player part: to update PreviewWidget
    connect( m_mediaPlayer, SIGNAL( playing() ),    this,   SIGNAL( playing() ), Qt::DirectConnection );
    connect( m_mediaPlayer, SIGNAL( paused() ),     this,   SIGNAL( paused() ), Qt::DirectConnection );
75
    connect( m_mediaPlayer, SIGNAL( errorEncountered() ), this, SLOT( errorEncountered() ) );
76 77
    //FIXME:: check if this doesn't require Qt::QueuedConnection
    connect( m_mediaPlayer, SIGNAL( stopped() ),    this,   SIGNAL( endReached() ) );
78 79
}

80
WorkflowRenderer::~WorkflowRenderer()
81
{
82
    killRenderer();
83

84 85
    if ( m_esHandler )
        delete m_esHandler;
86 87
    if ( m_media )
        delete m_media;
88 89
    if ( m_silencedAudioBuffer )
        delete m_silencedAudioBuffer;
90
    delete m_effectsLock;
91 92
}

93
void
94
WorkflowRenderer::setupRenderer( quint32 width, quint32 height, double fps )
95
{
96 97 98
    char        videoString[512];
    char        inputSlave[256];
    char        audioParameters[256];
99
    char        buffer[64];
100

101
    m_esHandler->fps = fps;
102
    //Clean any previous render.
103

104
    sprintf( videoString, "width=%i:height=%i:dar=%s:fps=%s:cookie=0:codec=%s:cat=2:caching=0",
105
             width, height, "16/9", "30/1", "RV32" );
106 107
    sprintf( audioParameters, "cookie=1:cat=1:codec=f32l:samplerate=%u:channels=%u:caching=0",
                m_rate, m_nbChannels );
108 109
    strcpy( inputSlave, ":input-slave=imem://" );
    strcat( inputSlave, audioParameters );
110 111 112

    if ( m_media != NULL )
        delete m_media;
113 114
    m_media = new LibVLCpp::Media( "imem://" + QString( videoString ) );
    m_media->addOption( inputSlave );
115

116
    sprintf( buffer, "imem-get=%"PRId64, (intptr_t)getLockCallback() );
117
    m_media->addOption( buffer );
118
    sprintf( buffer, ":imem-release=%"PRId64, (intptr_t)getUnlockCallback() );
119
    m_media->addOption( buffer );
120
    sprintf( buffer, ":imem-data=%"PRId64, (intptr_t)m_esHandler );
121
    m_media->addOption( buffer );
122 123 124
    m_media->addOption( ":text-renderer dummy" );
}

125
int
126
WorkflowRenderer::lock( void *datas, const char* cookie, qint64 *dts, qint64 *pts,
127
                        quint32 *flags, size_t *bufferSize, const void **buffer )
128
{
129
    int             ret = 1;
130 131
    EsHandler*      handler = reinterpret_cast<EsHandler*>( datas );
    bool            paused = handler->self->m_paused;
132

133 134
    *dts = -1;
    *flags = 0;
135 136 137 138 139 140 141
    if ( cookie == NULL || ( cookie[0] != WorkflowRenderer::VideoCookie &&
                             cookie[0] != WorkflowRenderer::AudioCookie ) )
    {
        qCritical() << "Invalid imem input cookie";
        return ret;
    }
    if ( cookie[0] == WorkflowRenderer::VideoCookie )
142
    {
143
        ret = handler->self->lockVideo( handler, pts, bufferSize, buffer );
144
        if ( paused == false )
145
            handler->self->m_mainWorkflow->nextFrame( Workflow::VideoTrack );
146
    }
147
    else if ( cookie[0] == WorkflowRenderer::AudioCookie )
148
    {
149
        ret = handler->self->lockAudio( handler, pts, bufferSize, buffer );
150
        if ( paused == false )
151
            handler->self->m_mainWorkflow->nextFrame( Workflow::AudioTrack );
152 153
    }
    else
154
        qCritical() << "Invalid imem cookie";
155
    return ret;
156 157
}

158
int
159
WorkflowRenderer::lockVideo( EsHandler *handler, qint64 *pts, size_t *bufferSize, const void **buffer )
160
{
161
    qint64                          ptsDiff = 0;
162
    Workflow::Frame                 *ret;
163

164 165
    if ( m_stopping == true )
        return 1;
166

167 168
    ret = static_cast<Workflow::Frame*>( m_mainWorkflow->getOutput( Workflow::VideoTrack, m_paused ) );
    ptsDiff = ret->ptsDiff;
169 170 171 172 173
    if ( ptsDiff == 0 )
    {
        //If no ptsDiff has been computed, we have to fake it, so we compute
        //the theorical pts for one frame.
        //this is a bit hackish though... (especially regarding the "no frame computed" detection)
174
        ptsDiff = 1000000 / handler->fps;
175
    }
176 177
    {
        QReadLocker lock( m_effectsLock );
178
        EffectsEngine::applyFilters( m_filters, ret,
179 180
                                     m_mainWorkflow->getCurrentFrame(),
                                     m_mainWorkflow->getCurrentFrame() * 1000.0 / handler->fps );
181
    }
182
    m_pts = *pts = ptsDiff + m_pts;
183 184
    *buffer = ret->buffer();
    *bufferSize = ret->size();
185 186 187
    return 0;
}

188
int
189
WorkflowRenderer::lockAudio( EsHandler *handler, qint64 *pts, size_t *bufferSize, const void ** buffer )
190
{
191
    qint64                              ptsDiff;
192
    quint32                             nbSample;
193
    Workflow::AudioSample               *renderAudioSample;
194

195
    if ( m_stopping == false && m_paused == false )
196
    {
197 198
        renderAudioSample = static_cast<Workflow::AudioSample*>( m_mainWorkflow->getOutput( Workflow::AudioTrack,
                                                                                           m_paused ) );
199
    }
200 201 202
    else
        renderAudioSample = NULL;
    if ( renderAudioSample != NULL )
203
    {
204
//        qDebug() << "pts diff:" << renderAudioSample->ptsDiff;
205 206 207 208
        nbSample = renderAudioSample->nbSample;
        *buffer = renderAudioSample->buff;
        *bufferSize = renderAudioSample->size;
        ptsDiff = renderAudioSample->ptsDiff;
209 210 211
    }
    else
    {
212
        nbSample = m_rate / handler->fps;
213
        unsigned int    buffSize = m_nbChannels * 2 * nbSample;
214 215 216 217
        if ( m_silencedAudioBuffer == NULL )
            m_silencedAudioBuffer = new uint8_t[ buffSize ];
        memset( m_silencedAudioBuffer, 0, buffSize );
        *buffer = m_silencedAudioBuffer;
218
        *bufferSize = buffSize;
219
        ptsDiff = m_pts - m_audioPts;
220
    }
221
    m_audioPts = *pts = m_audioPts + ptsDiff;
222
    return 0;
223 224
}

225
void    WorkflowRenderer::unlock( void*, const char*, size_t, void* )
226
{
227 228
}

229
void        WorkflowRenderer::startPreview()
230
{
231
    if ( m_mainWorkflow->getLengthFrame() <= 0 )
232
        return ;
233 234 235 236 237 238 239
    if ( paramsHasChanged( m_width, m_height, m_outputFps ) == true )
    {
        m_width = width();
        m_height = height();
        m_outputFps = outputFps();
        setupRenderer( m_width, m_height, m_outputFps );
    }
240
    QReadLocker     lock( m_effectsLock );
241
    EffectsEngine::initFilters( m_filters, m_width, m_height );
242

243
    //Deactivating vlc's keyboard inputs.
244
    m_mediaPlayer->setKeyInput( false );
245 246
    m_mediaPlayer->setMedia( m_media );

247
    m_mainWorkflow->setFullSpeedRender( false );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
248
    m_mainWorkflow->startRender( m_width, m_height, m_outputFps );
249
    m_isRendering = true;
250
    m_paused = false;
251
    m_stopping = false;
252
    m_pts = 0;
253
    m_audioPts = 0;
254
    m_mediaPlayer->play();
255 256
}

257 258
void        WorkflowRenderer::nextFrame()
{
259 260
    if ( m_paused == true )
        m_mainWorkflow->renderOneFrame();
261 262
}

263
void        WorkflowRenderer::previousFrame()
264
{
265
    if ( m_paused == true )
266
        m_mainWorkflow->previousFrame( Workflow::VideoTrack );
267 268
}

269
void        WorkflowRenderer::togglePlayPause( bool forcePause )
270
{
271
    if ( m_isRendering == false && forcePause == false )
272
        startPreview();
273 274 275 276 277 278 279 280
    else
        internalPlayPause( forcePause );
}

void        WorkflowRenderer::internalPlayPause( bool forcePause )
{
    //If force pause is true, we just ensure that this render is paused... no need to start it.
    if ( m_isRendering == true )
281
    {
282 283
        if ( m_paused == true && forcePause == false )
        {
284 285
            m_paused = false;
            emit playing();
286
        }
287
        else
288
        {
289 290
            if ( m_paused == false )
            {
291 292
                m_paused = true;
                emit paused();
293
            }
294
        }
295
    }
296
}
297

298
void        WorkflowRenderer::stop()
299 300 301
{
    //Since we want permanent render (to have a permanent render update, we shouldn't
    //stop, but pause
302 303 304
//    togglePlayPause( true );
//    m_mainWorkflow->setCurrentFrame( 0, MainWorkflow::Renderer );
    killRenderer();
305 306 307 308
}

void
WorkflowRenderer::killRenderer()
309 310
{
    m_isRendering = false;
311
    m_paused = false;
312
    m_stopping = true;
313
    m_mainWorkflow->stopFrameComputing();
314
    m_mediaPlayer->stop();
315
    m_mainWorkflow->stop();
316 317
    delete[] m_silencedAudioBuffer;
    m_silencedAudioBuffer = NULL;
318 319
}

320 321 322 323 324
qint64      WorkflowRenderer::getCurrentFrame() const
{
    return m_mainWorkflow->getCurrentFrame();
}

325 326 327 328 329
qint64      WorkflowRenderer::getLength() const
{
    return qRound64( (qreal)getLengthMs() / 1000.0 * (qreal)getFps() );
}

330 331
qint64      WorkflowRenderer::getLengthMs() const
{
Ludovic Fauvet's avatar
Ludovic Fauvet committed
332
    return m_mainWorkflow->getLengthFrame() / getFps() * 1000;
333 334 335 336
}

float       WorkflowRenderer::getFps() const
{
337
    return m_outputFps;
338 339
}

340 341 342 343 344 345 346 347 348 349
void        WorkflowRenderer::timelineCursorChanged( qint64 newFrame )
{
    m_mainWorkflow->setCurrentFrame( newFrame, MainWorkflow::TimelineCursor );
}

void        WorkflowRenderer::previewWidgetCursorChanged( qint64 newFrame )
{
    m_mainWorkflow->setCurrentFrame( newFrame, MainWorkflow::PreviewCursor );
}

350 351 352
void        WorkflowRenderer::rulerCursorChanged( qint64 newFrame )
{
    m_mainWorkflow->setCurrentFrame( newFrame, MainWorkflow::RulerCursor );
353 354
}

355 356 357 358 359 360 361 362 363 364
void*   WorkflowRenderer::getLockCallback()
{
    return (void*)&WorkflowRenderer::lock;
}

void*   WorkflowRenderer::getUnlockCallback()
{
    return (void*)&WorkflowRenderer::unlock;
}

365 366 367
quint32
WorkflowRenderer::width() const
{
368
    return VLMC_PROJECT_GET_UINT( "video/VideoProjectWidth" );
369 370 371 372 373
}

quint32
WorkflowRenderer::height() const
{
374
    return VLMC_PROJECT_GET_UINT( "video/VideoProjectHeight" );
375 376
}

Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
377 378 379
float
WorkflowRenderer::outputFps() const
{
380
    return VLMC_PROJECT_GET_DOUBLE( "video/VLMCOutputFPS" );
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
381 382
}

383
bool
384
WorkflowRenderer::paramsHasChanged( quint32 width, quint32 height, double fps )
385
{
386 387
    quint32             newWidth = this->width();
    quint32             newHeight = this->height();
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
388 389
    float               newOutputFps = outputFps();

390 391
    return ( newWidth != width || newHeight != height ||
         newOutputFps != fps );
392 393
}

394 395 396
void
WorkflowRenderer::appendEffect( Effect *effect, qint64 start, qint64 end )
{
397 398 399 400 401 402 403
    if ( effect->type() != Effect::Filter )
    {
        qWarning() << "WorkflowRenderer does not handle non filter effects.";
        return ;
    }
    FilterInstance  *filterInstance = static_cast<FilterInstance*>( effect->createInstance() );

404
    if ( isRendering() == true )
405
        filterInstance->init( m_width, m_height );
406
    QWriteLocker    lock( m_effectsLock );
407
    m_filters.push_back( new EffectsEngine::FilterHelper( filterInstance, start, end ) );
408 409
}

410 411 412 413 414 415
void
WorkflowRenderer::saveProject( QXmlStreamWriter &project ) const
{
    project.writeStartElement( "renderer" );
    {
        QReadLocker     lock( m_effectsLock );
416
        EffectsEngine::saveFilters( m_filters, project );
417 418 419 420
    }
    project.writeEndElement();
}

421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
void
WorkflowRenderer::loadProject( const QDomElement &project )
{
    QDomElement     renderer = project.firstChildElement( "renderer" );
    if ( renderer.isNull() == true )
        return ;
    QDomElement     effects = renderer.firstChildElement( "effects" );
    if ( effects.isNull() == true )
        return ;
    QDomElement     effect = effects.firstChildElement( "effect" );
    while ( effect.isNull() == false )
    {
        if ( effect.hasAttribute( "name" ) == true &&
             effect.hasAttribute( "start" ) == true &&
             effect.hasAttribute( "end" ) == true )
        {
            Effect  *e = EffectsEngine::getInstance()->effect( effect.attribute( "name" ) );
            if ( e != NULL )
                appendEffect( e, effect.attribute( "start" ).toLongLong(),
                              effect.attribute( "end" ).toLongLong() );
            else
                qCritical() << "Renderer: Can't load effect" << effect.attribute( "name" );
        }
444
        effect = effect.nextSiblingElement();
445 446 447
    }
}

448 449 450 451
/////////////////////////////////////////////////////////////////////
/////SLOTS :
/////////////////////////////////////////////////////////////////////

452
void        WorkflowRenderer::__endReached()
453
{
454
    stop();
455
    emit endReached();
456 457
}

458
void
459
WorkflowRenderer::mainWorkflowLenghtChanged( qint64 /*newLength*/ )
460
{
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
//    if ( newLength > 0 )
//    {
//        if ( m_oldLength == 0 )
//        {
//            if ( m_isRendering == false )
//                startPreview();
//            m_paused = false;
//            togglePlayPause( true );
//        }
//    }
//    else if ( newLength == 0 && m_isRendering == true )
//    {
//        stop();
//    }
//    m_oldLength = newLength;
476
}
477 478 479 480 481 482 483

void
WorkflowRenderer::errorEncountered()
{
    stop();
    emit error();
}