WorkflowRenderer.cpp 13.2 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_silencedAudioBuffer( NULL ),
50
            m_esHandler( NULL ),
51 52
            m_oldLength( 0 ),
            m_effectFrame( NULL )
53
{
54 55
}

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

62
    m_nbChannels = 2;
63
    m_rate = 48000;
64

65
     //Workflow part
66
    connect( m_mainWorkflow, SIGNAL( mainWorkflowEndReached() ), this, SLOT( __endReached() ), Qt::QueuedConnection );
67 68
    connect( m_mainWorkflow, SIGNAL( frameChanged( qint64, Vlmc::FrameChangedReason ) ),
             this, SIGNAL( frameChanged( qint64, Vlmc::FrameChangedReason ) ) );
69 70
    connect( m_mainWorkflow, SIGNAL( lengthChanged( qint64 ) ),
             this, SLOT(mainWorkflowLenghtChanged(qint64) ) );
71 72 73
    //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 );
74
    connect( m_mediaPlayer, SIGNAL( errorEncountered() ), this, SLOT( errorEncountered() ) );
75 76
    //FIXME:: check if this doesn't require Qt::QueuedConnection
    connect( m_mediaPlayer, SIGNAL( stopped() ),    this,   SIGNAL( endReached() ) );
77 78
}

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

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

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

99
    m_esHandler->fps = fps;
100
    //Clean any previous render.
101

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

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

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

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

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

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

162 163
    if ( m_stopping == true )
        return 1;
164

165
    ret = static_cast<const Workflow::Frame*>( m_mainWorkflow->getOutput( Workflow::VideoTrack, m_paused ) );
166
    ptsDiff = ret->ptsDiff;
167 168 169 170 171
    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)
172
        ptsDiff = 1000000 / handler->fps;
173
    }
174 175
    m_effectFrame = applyFilters( ret, m_mainWorkflow->getCurrentFrame(),
                                      m_mainWorkflow->getCurrentFrame() * 1000.0 / handler->fps );
176
    m_pts = *pts = ptsDiff + m_pts;
177 178 179 180
    if ( m_effectFrame != NULL )
        *buffer = m_effectFrame;
    else
        *buffer = ret->buffer();
181
    *bufferSize = ret->size();
182 183 184
    return 0;
}

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

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

222 223
void
WorkflowRenderer::unlock( void *datas, const char*, size_t, void* )
224
{
225 226 227
    EsHandler*      handler = reinterpret_cast<EsHandler*>( datas );
    delete[] handler->self->m_effectFrame;
    handler->self->m_effectFrame = NULL;
228 229
}

230 231
void
WorkflowRenderer::startPreview()
232
{
233
    if ( m_mainWorkflow->getLengthFrame() <= 0 )
234
        return ;
235 236 237 238 239 240 241
    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 );
    }
242
    initFilters();
243

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, m_outputFps );
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 262
    if ( m_paused == true )
        m_mainWorkflow->renderOneFrame();
263 264
}

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

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

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

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

void
WorkflowRenderer::killRenderer()
315 316
{
    m_isRendering = false;
317
    m_paused = false;
318
    m_stopping = true;
319
    m_mainWorkflow->stopFrameComputing();
320
    m_mediaPlayer->stop();
321
    m_mainWorkflow->stop();
322 323
    delete[] m_silencedAudioBuffer;
    m_silencedAudioBuffer = NULL;
324 325
}

326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
int
WorkflowRenderer::getVolume() const
{
    return m_mediaPlayer->getVolume();
}

int
WorkflowRenderer::setVolume( int volume )
{
    //Returns 0 if the volume was set, -1 if it was out of range
    return m_mediaPlayer->setVolume( volume );
}

qint64
WorkflowRenderer::getCurrentFrame() const
341 342 343 344
{
    return m_mainWorkflow->getCurrentFrame();
}

345 346
qint64
WorkflowRenderer::length() const
347 348 349 350
{
    return qRound64( (qreal)getLengthMs() / 1000.0 * (qreal)getFps() );
}

351 352
qint64
WorkflowRenderer::getLengthMs() const
353
{
354
    return m_mainWorkflow->getLengthFrame() / getFps() * 1000;
355 356
}

357 358
float
WorkflowRenderer::getFps() const
359
{
360
    return m_outputFps;
361 362
}

363 364
void
WorkflowRenderer::timelineCursorChanged( qint64 newFrame )
365
{
366
    m_mainWorkflow->setCurrentFrame( newFrame, Vlmc::TimelineCursor );
367 368
}

369 370
void
WorkflowRenderer::previewWidgetCursorChanged( qint64 newFrame )
371
{
372
    m_mainWorkflow->setCurrentFrame( newFrame, Vlmc::PreviewCursor );
373 374
}

375 376
void
WorkflowRenderer::rulerCursorChanged( qint64 newFrame )
377
{
378
    m_mainWorkflow->setCurrentFrame( newFrame, Vlmc::RulerCursor );
379 380
}

381 382
void*
WorkflowRenderer::getLockCallback()
383 384 385 386
{
    return (void*)&WorkflowRenderer::lock;
}

387 388
void*
WorkflowRenderer::getUnlockCallback()
389 390 391 392
{
    return (void*)&WorkflowRenderer::unlock;
}

393 394 395
quint32
WorkflowRenderer::width() const
{
396
    return VLMC_PROJECT_GET_UINT( "video/VideoProjectWidth" );
397 398 399 400 401
}

quint32
WorkflowRenderer::height() const
{
402
    return VLMC_PROJECT_GET_UINT( "video/VideoProjectHeight" );
403 404
}

405 406 407
float
WorkflowRenderer::outputFps() const
{
408
    return VLMC_PROJECT_GET_DOUBLE( "video/VLMCOutputFPS" );
409 410
}

411
bool
412
WorkflowRenderer::paramsHasChanged( quint32 width, quint32 height, double fps )
413
{
414 415
    quint32             newWidth = this->width();
    quint32             newHeight = this->height();
416 417
    float               newOutputFps = outputFps();

418 419
    return ( newWidth != width || newHeight != height ||
         newOutputFps != fps );
420 421
}

422 423 424 425
void
WorkflowRenderer::saveProject( QXmlStreamWriter &project ) const
{
    project.writeStartElement( "renderer" );
426
    saveFilters( project );
427 428 429
    project.writeEndElement();
}

430 431 432 433 434 435
void
WorkflowRenderer::loadProject( const QDomElement &project )
{
    QDomElement     renderer = project.firstChildElement( "renderer" );
    if ( renderer.isNull() == true )
        return ;
436
    loadEffects( renderer );
437 438
}

439 440 441 442
/////////////////////////////////////////////////////////////////////
/////SLOTS :
/////////////////////////////////////////////////////////////////////

443 444
void
WorkflowRenderer::__endReached()
445
{
446
    stop();
447
    emit endReached();
448 449
}

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

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