WorkflowRenderer.cpp 13.6 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_aspectRatio( "" ),
50
            m_silencedAudioBuffer( NULL ),
51
            m_esHandler( NULL ),
52 53
            m_oldLength( 0 ),
            m_effectFrame( NULL )
54
{
55 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 69
    connect( m_mainWorkflow, SIGNAL( frameChanged( qint64, Vlmc::FrameChangedReason ) ),
             this, SIGNAL( frameChanged( qint64, Vlmc::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 91
}

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

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

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

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

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

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

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

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

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

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

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

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

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

231 232
void
WorkflowRenderer::startPreview()
233
{
234
    if ( m_mainWorkflow->getLengthFrame() <= 0 )
235
        return ;
236
    if ( paramsHasChanged( m_width, m_height, m_outputFps, m_aspectRatio ) == true )
237 238 239 240
    {
        m_width = width();
        m_height = height();
        m_outputFps = outputFps();
241
        m_aspectRatio = aspectRatio();
242
        setupRenderer( m_width, m_height, m_outputFps );
243
        qDebug() << "yo setup renderer";
244
    }
245
    initFilters();
246

247
    //Deactivating vlc's keyboard inputs.
248
    m_mediaPlayer->setKeyInput( false );
249 250
    m_mediaPlayer->setMedia( m_media );

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

261 262
void
WorkflowRenderer::nextFrame()
263
{
264 265
    if ( m_paused == true )
        m_mainWorkflow->renderOneFrame();
266 267
}

268 269
void
WorkflowRenderer::previousFrame()
270
{
271
    if ( m_paused == true )
272
        m_mainWorkflow->previousFrame( Workflow::VideoTrack );
273 274
}

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

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

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

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

329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
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
344 345 346 347
{
    return m_mainWorkflow->getCurrentFrame();
}

348 349
qint64
WorkflowRenderer::length() const
350 351 352 353
{
    return qRound64( (qreal)getLengthMs() / 1000.0 * (qreal)getFps() );
}

354 355
qint64
WorkflowRenderer::getLengthMs() const
356
{
357
    return m_mainWorkflow->getLengthFrame() / getFps() * 1000;
358 359
}

360 361
float
WorkflowRenderer::getFps() const
362
{
363
    return m_outputFps;
364 365
}

366 367
void
WorkflowRenderer::timelineCursorChanged( qint64 newFrame )
368
{
369
    m_mainWorkflow->setCurrentFrame( newFrame, Vlmc::TimelineCursor );
370 371
}

372 373
void
WorkflowRenderer::previewWidgetCursorChanged( qint64 newFrame )
374
{
375
    m_mainWorkflow->setCurrentFrame( newFrame, Vlmc::PreviewCursor );
376 377
}

378 379
void
WorkflowRenderer::rulerCursorChanged( qint64 newFrame )
380
{
381
    m_mainWorkflow->setCurrentFrame( newFrame, Vlmc::RulerCursor );
382 383
}

384 385
void*
WorkflowRenderer::getLockCallback()
386 387 388 389
{
    return (void*)&WorkflowRenderer::lock;
}

390 391
void*
WorkflowRenderer::getUnlockCallback()
392 393 394 395
{
    return (void*)&WorkflowRenderer::unlock;
}

396 397 398
quint32
WorkflowRenderer::width() const
{
399
    return VLMC_PROJECT_GET_UINT( "video/VideoProjectWidth" );
400 401 402 403 404
}

quint32
WorkflowRenderer::height() const
{
405
    return VLMC_PROJECT_GET_UINT( "video/VideoProjectHeight" );
406 407
}

408 409 410
float
WorkflowRenderer::outputFps() const
{
411
    return VLMC_PROJECT_GET_DOUBLE( "video/VLMCOutputFPS" );
412 413
}

414 415 416 417 418 419
const QString
WorkflowRenderer::aspectRatio() const
{
    return VLMC_PROJECT_GET_STRING("video/AspectRatio");
}

420
bool
421
WorkflowRenderer::paramsHasChanged( quint32 width, quint32 height, double fps, QString aspect )
422
{
423 424
    quint32             newWidth = this->width();
    quint32             newHeight = this->height();
425
    float               newOutputFps = outputFps();
426
    const QString       newAspectRatio = aspectRatio();
427

428
    return ( newWidth != width || newHeight != height ||
429
         newOutputFps != fps || newAspectRatio != aspect );
430 431
}

432 433 434 435
void
WorkflowRenderer::saveProject( QXmlStreamWriter &project ) const
{
    project.writeStartElement( "renderer" );
436
    saveFilters( project );
437 438 439
    project.writeEndElement();
}

440 441 442 443 444 445
void
WorkflowRenderer::loadProject( const QDomElement &project )
{
    QDomElement     renderer = project.firstChildElement( "renderer" );
    if ( renderer.isNull() == true )
        return ;
446
    loadEffects( renderer );
447 448
}

449 450 451 452
/////////////////////////////////////////////////////////////////////
/////SLOTS :
/////////////////////////////////////////////////////////////////////

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

460
void
461
WorkflowRenderer::mainWorkflowLenghtChanged( qint64 /*newLength*/ )
462
{
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477
//    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;
478
}
479 480 481 482 483 484 485

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