Credit Andrew - improve in-tree documentation
[goodguy/cinelerra.git] / cinelerra / renderengine.C
1
2 /*
3  * CINELERRA
4  * Copyright (C) 2009 Adam Williams <broadcast at earthling dot net>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  */
21
22 #include "amodule.h"
23 #include "arender.h"
24 #include "asset.h"
25 #include "audiodevice.h"
26 #include "bcsignals.h"
27 #include "condition.h"
28 #include "edl.h"
29 #include "edlsession.h"
30 #include "meterhistory.h"
31 #include "mutex.h"
32 #include "mwindow.h"
33 #include "playbackengine.h"
34 #include "preferences.h"
35 #include "preferencesthread.h"
36 #include "renderengine.h"
37 #include "mainsession.h"
38 #include "tracks.h"
39 #include "transportque.h"
40 #include "videodevice.h"
41 #include "vrender.h"
42 #include "workarounds.h"
43
44
45
46 RenderEngine::RenderEngine(PlaybackEngine *playback_engine,
47         Preferences *preferences,
48         Canvas *output,
49         int is_nested)
50  : Thread(1, 0, 0)
51 {
52         this->playback_engine = playback_engine;
53         this->output = output;
54         this->is_nested = is_nested;
55         audio = 0;
56         video = 0;
57         config = new PlaybackConfig;
58         arender = 0;
59         vrender = 0;
60         do_audio = 0;
61         do_video = 0;
62         interrupted = 0;
63         this->preferences = new Preferences;
64         this->command = new TransportCommand;
65         this->preferences->copy_from(preferences);
66         edl = 0;
67
68         audio_cache = 0;
69         video_cache = 0;
70         mwindow = !playback_engine ? 0 : playback_engine->mwindow;
71
72         input_lock = new Condition(1, "RenderEngine::input_lock");
73         start_lock = new Condition(1, "RenderEngine::start_lock");
74         output_lock = new Condition(1, "RenderEngine::output_lock");
75         render_active = new Condition(1,"RenderEngine::render_active");
76         interrupt_lock = new Condition(1, "RenderEngine::interrupt_lock");
77         first_frame_lock = new Condition(1, "RenderEngine::first_frame_lock");
78 }
79
80 RenderEngine::~RenderEngine()
81 {
82         close_output();
83         delete command;
84         delete preferences;
85         if(arender) delete arender;
86         if(vrender) delete vrender;
87         delete input_lock;
88         delete start_lock;
89         delete output_lock;
90         delete render_active;
91         delete interrupt_lock;
92         delete first_frame_lock;
93         delete config;
94         if( edl ) edl->Garbage::remove_user();
95 }
96
97 EDL* RenderEngine::get_edl()
98 {
99 //      return command->get_edl();
100         return edl;
101 }
102
103 int RenderEngine::arm_command(TransportCommand *command)
104 {
105         const int debug = 0;
106 // Prevent this renderengine from accepting another command until finished.
107 // Since the renderengine is often deleted after the input_lock command it must
108 // be locked here as well as in the calling routine.
109         if(debug) printf("RenderEngine::arm_command %d\n", __LINE__);
110
111
112         input_lock->lock("RenderEngine::arm_command");
113
114         if(!edl)
115         {
116                 edl = new EDL;
117                 edl->create_objects();
118                 edl->copy_all(command->get_edl());
119         }
120         this->command->copy_from(command);
121
122 // Fix background rendering asset to use current dimensions and ignore
123 // headers.
124         preferences->brender_asset->frame_rate = command->get_edl()->session->frame_rate;
125         preferences->brender_asset->width = command->get_edl()->session->output_w;
126         preferences->brender_asset->height = command->get_edl()->session->output_h;
127         preferences->brender_asset->use_header = 0;
128         preferences->brender_asset->layers = 1;
129         preferences->brender_asset->video_data = 1;
130
131         done = 0;
132         interrupted = 0;
133
134 // Retool configuration for this node
135         this->config->copy_from(command->get_edl()->session->playback_config);
136         VideoOutConfig *vconfig = this->config->vconfig;
137         AudioOutConfig *aconfig = this->config->aconfig;
138         if(command->realtime)
139         {
140                 if(command->single_frame() && vconfig->driver != PLAYBACK_X11_GL)
141                 {
142                         vconfig->driver = PLAYBACK_X11;
143                 }
144         }
145         else
146         {
147                 vconfig->driver = PLAYBACK_X11;
148         }
149
150
151
152         get_duty();
153
154         if(do_audio)
155         {
156                 fragment_len = aconfig->fragment_size;
157 // Larger of audio_module_fragment and fragment length adjusted for speed
158 // Extra memory must be allocated for rendering slow motion.
159                 float speed = command->get_speed();
160                 adjusted_fragment_len = speed >= 1.0 ?
161                         (int64_t)(aconfig->fragment_size * speed + 0.5) :
162                         (int64_t)(aconfig->fragment_size / speed + 0.5) ;
163                 if(adjusted_fragment_len < aconfig->fragment_size)
164                         adjusted_fragment_len = aconfig->fragment_size;
165         }
166
167 // Set lock so audio doesn't start until video has started.
168         if(do_video)
169         {
170                 while(first_frame_lock->get_value() > 0)
171                         first_frame_lock->lock("RenderEngine::arm_command");
172         }
173         else
174 // Set lock so audio doesn't wait for video which is never to come.
175         {
176                 while(first_frame_lock->get_value() <= 0)
177                         first_frame_lock->unlock();
178         }
179
180         open_output();
181         create_render_threads();
182         arm_render_threads();
183         if(debug) printf("RenderEngine::arm_command %d\n", __LINE__);
184
185         return 0;
186 }
187
188 void RenderEngine::get_duty()
189 {
190         do_audio = 0;
191         do_video = 0;
192
193 //printf("RenderEngine::get_duty %d\n", __LINE__);
194         if( get_edl()->tracks->playable_audio_tracks() &&
195             get_edl()->session->audio_channels )
196         {
197                 do_audio = !command->single_frame() ? 1 : 0;
198                 if( command->audio_toggle ) do_audio = !do_audio;
199         }
200
201 //printf("RenderEngine::get_duty %d\n", __LINE__);
202         if(get_edl()->tracks->playable_video_tracks())
203         {
204 //printf("RenderEngine::get_duty %d\n", __LINE__);
205                 do_video = 1;
206         }
207 }
208
209 void RenderEngine::create_render_threads()
210 {
211         if(do_video && !vrender)
212         {
213                 vrender = new VRender(this);
214         }
215
216         if(do_audio && !arender)
217         {
218                 arender = new ARender(this);
219         }
220 }
221
222
223 int RenderEngine::get_output_w()
224 {
225         return get_edl()->session->output_w;
226 }
227
228 int RenderEngine::get_output_h()
229 {
230         return get_edl()->session->output_h;
231 }
232
233 int RenderEngine::brender_available(int position, int direction)
234 {
235         if(playback_engine)
236         {
237                 int64_t corrected_position = position;
238                 if(direction == PLAY_REVERSE)
239                         corrected_position--;
240                 return playback_engine->brender_available(corrected_position);
241         }
242         else
243                 return 0;
244 }
245
246
247 CICache* RenderEngine::get_acache()
248 {
249         if(playback_engine)
250                 return playback_engine->audio_cache;
251         else
252                 return audio_cache;
253 }
254
255 CICache* RenderEngine::get_vcache()
256 {
257         if(playback_engine)
258                 return playback_engine->video_cache;
259         else
260                 return video_cache;
261 }
262
263 void RenderEngine::set_acache(CICache *cache)
264 {
265         this->audio_cache = cache;
266 }
267
268 void RenderEngine::set_vcache(CICache *cache)
269 {
270         this->video_cache = cache;
271 }
272
273
274 double RenderEngine::get_tracking_position()
275 {
276         if(playback_engine)
277                 return playback_engine->get_tracking_position();
278         else
279                 return 0;
280 }
281
282 int RenderEngine::open_output()
283 {
284         if(command->realtime && !is_nested)
285         {
286 // Allocate devices
287                 if(do_audio)
288                 {
289                         audio = new AudioDevice(mwindow);
290                 }
291
292                 if(do_video)
293                 {
294                         video = new VideoDevice(mwindow);
295                 }
296
297 // Initialize sharing
298
299
300 // Start playback
301                 if(do_audio && do_video)
302                 {
303                         video->set_adevice(audio);
304                         audio->set_vdevice(video);
305                 }
306
307
308
309 // Retool playback configuration
310                 if(do_audio)
311                 {
312                         if(audio->open_output(config->aconfig,
313                                 get_edl()->session->sample_rate,
314                                 adjusted_fragment_len,
315                                 get_edl()->session->audio_channels,
316                                 get_edl()->session->real_time_playback))
317                                 do_audio = 0;
318                         else
319                         {
320                                 audio->set_software_positioning(
321                                         get_edl()->session->playback_software_position);
322                                 audio->start_playback();
323                         }
324                 }
325
326                 if(do_video)
327                 {
328                         video->open_output(config->vconfig,
329                                 get_edl()->session->frame_rate,
330                                 get_output_w(),
331                                 get_output_h(),
332                                 output,
333                                 command->single_frame());
334                         video->set_quality(80);
335                         video->set_cpus(preferences->processors);
336                 }
337         }
338
339         return 0;
340 }
341
342 void RenderEngine::reset_sync_position()
343 {
344         timer.update();
345 }
346
347 int64_t RenderEngine::sync_position()
348 {
349 // Use audio device
350 // No danger of race conditions because the output devices are closed after all
351 // threads join.
352         if(do_audio)
353         {
354                 return audio->current_position();
355         }
356
357         if(do_video)
358         {
359                 int64_t result = timer.get_scaled_difference(
360                         get_edl()->session->sample_rate);
361                 return result;
362         }
363         return 0;
364 }
365
366
367 int RenderEngine::start_command()
368 {
369         if(command->realtime && !is_nested)
370         {
371                 interrupt_lock->lock("RenderEngine::start_command");
372                 start_lock->lock("RenderEngine::start_command 1");
373                 Thread::start();
374                 start_lock->lock("RenderEngine::start_command 2");
375                 start_lock->unlock();
376         }
377         return 0;
378 }
379
380 void RenderEngine::arm_render_threads()
381 {
382         if(do_audio)
383         {
384                 arender->arm_command();
385         }
386
387         if(do_video)
388         {
389                 vrender->arm_command();
390         }
391 }
392
393
394 void RenderEngine::start_render_threads()
395 {
396 // Synchronization timer.  Gets reset once again after the first video frame.
397         timer.update();
398
399         if(do_audio)
400         {
401                 arender->start_command();
402         }
403
404         if(do_video)
405         {
406                 vrender->start_command();
407         }
408 }
409
410 void RenderEngine::update_framerate(float framerate)
411 {
412         playback_engine->mwindow->session->actual_frame_rate = framerate;
413         playback_engine->mwindow->preferences_thread->update_framerate();
414 }
415
416 void RenderEngine::wait_render_threads()
417 {
418         if(do_audio)
419         {
420                 arender->Thread::join();
421         }
422
423         if(do_video)
424         {
425                 vrender->Thread::join();
426         }
427 }
428
429 void RenderEngine::interrupt_playback()
430 {
431         interrupt_lock->lock("RenderEngine::interrupt_playback");
432         interrupted = 1;
433         if(do_audio && arender)
434         {
435                 arender->interrupt_playback();
436         }
437
438         if(do_video && vrender)
439         {
440                 vrender->interrupt_playback();
441         }
442         interrupt_lock->unlock();
443 }
444
445 int RenderEngine::close_output()
446 {
447 // Nested engines share devices
448         if(!is_nested)
449         {
450                 if(audio)
451                 {
452                         audio->close_all();
453                         delete audio;
454                         audio = 0;
455                 }
456
457
458
459                 if(video)
460                 {
461                         video->close_all();
462                         delete video;
463                         video = 0;
464                 }
465         }
466
467         return 0;
468 }
469
470 void RenderEngine::get_output_levels(double *levels, int64_t position)
471 {
472         if( do_audio ) {
473                 MeterHistory *meter_history = arender->meter_history;
474                 int64_t tolerance = 4*arender->meter_render_fragment;
475                 int pos = meter_history->get_nearest(position, tolerance);
476                 for( int i=0; i<MAXCHANNELS; ++i ) {
477                         if( !arender->audio_out[i] ) continue;
478                         levels[i] = meter_history->get_peak(i, pos);
479                 }
480         }
481 }
482
483 void RenderEngine::get_module_levels(ArrayList<double> *module_levels, int64_t position)
484 {
485         if( do_audio ) {
486                 int64_t tolerance = 4*arender->meter_render_fragment;
487                 for( int i=0; i<arender->total_modules; ++i ) {
488                         AModule *amodule = (AModule *)arender->modules[i];
489                         MeterHistory *meter_history = amodule->meter_history;
490                         int pos = meter_history->get_nearest(position, tolerance);
491                         module_levels->append(meter_history->get_peak(0, pos));
492                 }
493         }
494 }
495
496
497 void RenderEngine::run()
498 {
499         render_active->lock("RenderEngine::run");
500         start_render_threads();
501         start_lock->unlock();
502         interrupt_lock->unlock();
503
504         wait_render_threads();
505
506         interrupt_lock->lock("RenderEngine::run");
507
508
509         if(interrupted)
510         {
511                 playback_engine->tracking_position = playback_engine->get_tracking_position();
512         }
513
514         close_output();
515
516 // Fix the tracking position
517         if(playback_engine)
518         {
519                 if(command->command == CURRENT_FRAME || command->command == LAST_FRAME)
520                 {
521 //printf("RenderEngine::run 4.1 %d\n", playback_engine->tracking_position);
522                         playback_engine->tracking_position = command->playbackstart;
523                 }
524                 else
525                 {
526 // Make sure transport doesn't issue a pause command next
527 //printf("RenderEngine::run 4.1 %d\n", playback_engine->tracking_position);
528                         if(!interrupted)
529                         {
530                                 playback_engine->tracking_position =
531                                         command->get_direction() == PLAY_FORWARD ?
532                                                 command->end_position : command->start_position;
533                         }
534
535                         if( playback_engine->is_playing_back && command->displacement ) {
536                                 double position = playback_engine->tracking_position -
537                                         1./command->get_edl()->session->frame_rate;
538                                 playback_engine->tracking_position = position >= 0 ? position : 0;
539                         }
540
541                         if(!interrupted) playback_engine->command->command = STOP;
542                         playback_engine->stop_tracking();
543
544                 }
545                 playback_engine->is_playing_back = 0;
546         }
547
548         input_lock->unlock();
549         interrupt_lock->unlock();
550         render_active->unlock();
551 }
552
553 void RenderEngine::wait_done()
554 {
555         render_active->lock("RenderEngine::wait_done");
556         render_active->unlock();
557 }
558
559