WorkflowRenderer.cpp 14.4 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 "EffectInstance.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 ),
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

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( MainWorkflow::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( MainWorkflow::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 162
    qint64                          ptsDiff = 0;
    MainWorkflow::OutputBuffers*    ret;
163

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

    ret = m_mainWorkflow->getOutput( MainWorkflow::VideoTrack, m_paused );
    ptsDiff = ret->video->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 179 180
        EffectsEngine::applyEffects( m_effects, ret->video,
                                     m_mainWorkflow->getCurrentFrame(),
                                     m_mainWorkflow->getCurrentFrame() * 1000.0 / handler->fps );
181
    }
182
    m_pts = *pts = ptsDiff + m_pts;
183
    *buffer = ret->video->buffer();
184
    *bufferSize = ret->video->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
        MainWorkflow::OutputBuffers* ret = m_mainWorkflow->getOutput( MainWorkflow::AudioTrack,
                                                                      m_paused );
199
        renderAudioSample = ret->audio;
200
    }
201 202 203
    else
        renderAudioSample = NULL;
    if ( renderAudioSample != NULL )
204
    {
205
//        qDebug() << "pts diff:" << renderAudioSample->ptsDiff;
206 207 208 209
        nbSample = renderAudioSample->nbSample;
        *buffer = renderAudioSample->buff;
        *bufferSize = renderAudioSample->size;
        ptsDiff = renderAudioSample->ptsDiff;
210 211 212
    }
    else
    {
213
        nbSample = m_rate / handler->fps;
214
        unsigned int    buffSize = m_nbChannels * 2 * nbSample;
215 216 217 218
        if ( m_silencedAudioBuffer == NULL )
            m_silencedAudioBuffer = new uint8_t[ buffSize ];
        memset( m_silencedAudioBuffer, 0, buffSize );
        *buffer = m_silencedAudioBuffer;
219
        *bufferSize = buffSize;
220
        ptsDiff = m_pts - m_audioPts;
221
    }
222
    m_audioPts = *pts = m_audioPts + ptsDiff;
223
    return 0;
224 225
}

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

230
void        WorkflowRenderer::startPreview()
231
{
232
    if ( m_mainWorkflow->getLengthFrame() <= 0 )
233
        return ;
234 235 236 237 238 239 240
    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 );
    }
241 242 243
    QReadLocker     lock( m_effectsLock );
    EffectsEngine::initEffects( m_effects, m_width, m_height );

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

248
    m_mainWorkflow->setFullSpeedRender( false );
249
    m_mainWorkflow->startRender( m_width, m_height );
250
    m_isRendering = true;
251
    m_paused = false;
252
    m_stopping = false;
253
    m_pts = 0;
254
    m_audioPts = 0;
255
    m_mediaPlayer->play();
256 257
}

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

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

270
void        WorkflowRenderer::togglePlayPause( bool forcePause )
271
{
272
    if ( m_isRendering == false && forcePause == false )
273
        startPreview();
274 275 276 277 278 279 280 281
    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 )
282
    {
283 284
        if ( m_paused == true && forcePause == false )
        {
285 286
            m_paused = false;
            emit playing();
287
        }
288
        else
289
        {
290 291
            if ( m_paused == false )
            {
292 293
                m_paused = true;
                emit paused();
294
            }
295
        }
296
    }
297
}
298

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

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

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

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

331 332
qint64      WorkflowRenderer::getLengthMs() const
{
333
    return m_mainWorkflow->getLengthFrame() / getFps() * 1000;
334 335 336 337
}

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

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

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

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

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

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

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

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

378 379 380
float
WorkflowRenderer::outputFps() const
{
381
    return VLMC_PROJECT_GET_DOUBLE( "video/VLMCOutputFPS" );
382 383
}

384
bool
385
WorkflowRenderer::paramsHasChanged( quint32 width, quint32 height, double fps )
386
{
387 388
    quint32             newWidth = this->width();
    quint32             newHeight = this->height();
389 390
    float               newOutputFps = outputFps();

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

395 396 397
void
WorkflowRenderer::appendEffect( Effect *effect, qint64 start, qint64 end )
{
398
    EffectInstance  *effectInstance = effect->createInstance();
399
    if ( isRendering() == true )
400
        effectInstance->init( m_width, m_height );
401
    QWriteLocker    lock( m_effectsLock );
402
    m_effects.push_back( new EffectsEngine::EffectHelper( effectInstance, start, end ) );
403 404
}

405 406 407 408 409 410
void
WorkflowRenderer::saveProject( QXmlStreamWriter &project ) const
{
    project.writeStartElement( "renderer" );
    {
        QReadLocker     lock( m_effectsLock );
411
        EffectsEngine::saveEffects( m_effects, project );
412 413 414 415
    }
    project.writeEndElement();
}

416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
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" );
        }
439
        effect = effect.nextSiblingElement();
440 441 442
    }
}

443 444 445 446
/////////////////////////////////////////////////////////////////////
/////SLOTS :
/////////////////////////////////////////////////////////////////////

447
void        WorkflowRenderer::__endReached()
448
{
449
    stop();
450
    emit endReached();
451 452
}

453
void
454
WorkflowRenderer::mainWorkflowLenghtChanged( qint64 /*newLength*/ )
455
{
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
//    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;
471
}
472 473 474 475 476 477 478

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