--- /dev/null
+
+/*
+ * CINELERRA
+ * Copyright (C) 2009 Adam Williams <broadcast at earthling dot net>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "amodule.h"
+#include "arender.h"
+#include "asset.h"
+#include "audiodevice.h"
+#include "bcsignals.h"
+#include "channeldb.h"
+#include "condition.h"
+#include "edl.h"
+#include "edlsession.h"
+#include "mutex.h"
+#include "mwindow.h"
+#include "playbackengine.h"
+#include "preferences.h"
+#include "preferencesthread.h"
+#include "renderengine.h"
+#include "mainsession.h"
+#include "tracks.h"
+#include "transportque.h"
+#include "videodevice.h"
+#include "vrender.h"
+#include "workarounds.h"
+
+
+
+RenderEngine::RenderEngine(PlaybackEngine *playback_engine,
+ Preferences *preferences,
+ Canvas *output,
+ ChannelDB *channeldb,
+ int is_nested)
+ : Thread(1, 0, 0)
+{
+ this->playback_engine = playback_engine;
+ this->output = output;
+ this->channeldb = channeldb;
+ this->is_nested = is_nested;
+ audio = 0;
+ video = 0;
+ config = new PlaybackConfig;
+ arender = 0;
+ vrender = 0;
+ do_audio = 0;
+ do_video = 0;
+ interrupted = 0;
+ this->preferences = new Preferences;
+ this->command = new TransportCommand;
+ this->preferences->copy_from(preferences);
+ edl = 0;
+
+ audio_cache = 0;
+ video_cache = 0;
+ if(playback_engine && playback_engine->mwindow)
+ mwindow = playback_engine->mwindow;
+ else
+ mwindow = 0;
+ show_tc = 0;
+
+
+ input_lock = new Condition(1, "RenderEngine::input_lock");
+ start_lock = new Condition(1, "RenderEngine::start_lock");
+ output_lock = new Condition(1, "RenderEngine::output_lock");
+ interrupt_lock = new Mutex("RenderEngine::interrupt_lock");
+ first_frame_lock = new Condition(1, "RenderEngine::first_frame_lock");
+}
+
+RenderEngine::~RenderEngine()
+{
+ close_output();
+ delete command;
+ delete preferences;
+ if(arender) delete arender;
+ if(vrender) delete vrender;
+ delete input_lock;
+ delete start_lock;
+ delete output_lock;
+ delete interrupt_lock;
+ delete first_frame_lock;
+ delete config;
+ edl->Garbage::remove_user();
+}
+
+EDL* RenderEngine::get_edl()
+{
+// return command->get_edl();
+ return edl;
+}
+
+int RenderEngine::arm_command(TransportCommand *command)
+{
+ const int debug = 0;
+// Prevent this renderengine from accepting another command until finished.
+// Since the renderengine is often deleted after the input_lock command it must
+// be locked here as well as in the calling routine.
+ if(debug) printf("RenderEngine::arm_command %d\n", __LINE__);
+
+
+ input_lock->lock("RenderEngine::arm_command");
+
+ if(!edl)
+ {
+ edl = new EDL;
+ edl->create_objects();
+ edl->copy_all(command->get_edl());
+ }
+ this->command->copy_from(command);
+
+// Fix background rendering asset to use current dimensions and ignore
+// headers.
+ preferences->brender_asset->frame_rate = command->get_edl()->session->frame_rate;
+ preferences->brender_asset->width = command->get_edl()->session->output_w;
+ preferences->brender_asset->height = command->get_edl()->session->output_h;
+ preferences->brender_asset->use_header = 0;
+ preferences->brender_asset->layers = 1;
+ preferences->brender_asset->video_data = 1;
+
+ done = 0;
+ interrupted = 0;
+
+// Retool configuration for this node
+ this->config->copy_from(command->get_edl()->session->playback_config);
+ VideoOutConfig *vconfig = this->config->vconfig;
+ AudioOutConfig *aconfig = this->config->aconfig;
+ if(command->realtime)
+ {
+ if(command->single_frame() && vconfig->driver != PLAYBACK_X11_GL)
+ {
+ vconfig->driver = PLAYBACK_X11;
+ }
+ }
+ else
+ {
+ vconfig->driver = PLAYBACK_X11;
+ }
+
+
+
+ get_duty();
+
+ if(do_audio)
+ {
+ fragment_len = aconfig->fragment_size;
+// Larger of audio_module_fragment and fragment length adjusted for speed
+// Extra memory must be allocated for rendering slow motion.
+ float speed = command->get_speed();
+ adjusted_fragment_len = speed >= 1.0 ?
+ (int64_t)(aconfig->fragment_size * speed + 0.5) :
+ (int64_t)(aconfig->fragment_size / speed + 0.5) ;
+ if(adjusted_fragment_len < aconfig->fragment_size)
+ adjusted_fragment_len = aconfig->fragment_size;
+ }
+
+// Set lock so audio doesn't start until video has started.
+ if(do_video)
+ {
+ while(first_frame_lock->get_value() > 0)
+ first_frame_lock->lock("RenderEngine::arm_command");
+ }
+ else
+// Set lock so audio doesn't wait for video which is never to come.
+ {
+ while(first_frame_lock->get_value() <= 0)
+ first_frame_lock->unlock();
+ }
+
+ open_output();
+ create_render_threads();
+ arm_render_threads();
+ if(debug) printf("RenderEngine::arm_command %d\n", __LINE__);
+
+ return 0;
+}
+
+void RenderEngine::get_duty()
+{
+ do_audio = 0;
+ do_video = 0;
+
+//printf("RenderEngine::get_duty %d\n", __LINE__);
+ if(!command->single_frame() &&
+ get_edl()->tracks->playable_audio_tracks() &&
+ get_edl()->session->audio_channels)
+ {
+ do_audio = 1;
+ }
+
+//printf("RenderEngine::get_duty %d\n", __LINE__);
+ if(get_edl()->tracks->playable_video_tracks())
+ {
+//printf("RenderEngine::get_duty %d\n", __LINE__);
+ do_video = 1;
+ }
+}
+
+void RenderEngine::create_render_threads()
+{
+ if(do_video && !vrender)
+ {
+ vrender = new VRender(this);
+ }
+
+ if(do_audio && !arender)
+ {
+ arender = new ARender(this);
+ }
+}
+
+
+int RenderEngine::get_output_w()
+{
+ return get_edl()->session->output_w;
+}
+
+int RenderEngine::get_output_h()
+{
+ return get_edl()->session->output_h;
+}
+
+int RenderEngine::brender_available(int position, int direction)
+{
+ if(playback_engine)
+ {
+ int64_t corrected_position = position;
+ if(direction == PLAY_REVERSE)
+ corrected_position--;
+ return playback_engine->brender_available(corrected_position);
+ }
+ else
+ return 0;
+}
+
+Channel* RenderEngine::get_current_channel()
+{
+ if(channeldb)
+ {
+ switch(config->vconfig->driver)
+ {
+ case PLAYBACK_BUZ:
+ if(config->vconfig->buz_out_channel >= 0 &&
+ config->vconfig->buz_out_channel < channeldb->size())
+ {
+ return channeldb->get(config->vconfig->buz_out_channel);
+ }
+ break;
+ case VIDEO4LINUX2JPEG:
+ case VIDEO4LINUX2MPEG:
+ break;
+ }
+ }
+ return 0;
+}
+
+CICache* RenderEngine::get_acache()
+{
+ if(playback_engine)
+ return playback_engine->audio_cache;
+ else
+ return audio_cache;
+}
+
+CICache* RenderEngine::get_vcache()
+{
+ if(playback_engine)
+ return playback_engine->video_cache;
+ else
+ return video_cache;
+}
+
+void RenderEngine::set_acache(CICache *cache)
+{
+ this->audio_cache = cache;
+}
+
+void RenderEngine::set_vcache(CICache *cache)
+{
+ this->video_cache = cache;
+}
+
+
+double RenderEngine::get_tracking_position()
+{
+ if(playback_engine)
+ return playback_engine->get_tracking_position();
+ else
+ return 0;
+}
+
+int RenderEngine::open_output()
+{
+ if(command->realtime && !is_nested)
+ {
+// Allocate devices
+ if(do_audio)
+ {
+ audio = new AudioDevice(mwindow);
+ }
+
+ if(do_video)
+ {
+ video = new VideoDevice(mwindow);
+ }
+
+// Initialize sharing
+
+
+// Start playback
+ if(do_audio && do_video)
+ {
+ video->set_adevice(audio);
+ audio->set_vdevice(video);
+ }
+
+
+
+// Retool playback configuration
+ if(do_audio)
+ {
+ if(audio->open_output(config->aconfig,
+ get_edl()->session->sample_rate,
+ adjusted_fragment_len,
+ get_edl()->session->audio_channels,
+ get_edl()->session->real_time_playback))
+ do_audio = 0;
+ else
+ {
+ audio->set_software_positioning(
+ get_edl()->session->playback_software_position);
+ audio->start_playback();
+ }
+ }
+
+ if(do_video)
+ {
+ video->open_output(config->vconfig,
+ get_edl()->session->frame_rate,
+ get_output_w(),
+ get_output_h(),
+ output,
+ command->single_frame());
+ Channel *channel = get_current_channel();
+ if(channel) video->set_channel(channel);
+ video->set_quality(80);
+ video->set_cpus(preferences->processors);
+ }
+ }
+
+ return 0;
+}
+
+void RenderEngine::reset_sync_position()
+{
+ timer.update();
+}
+
+int64_t RenderEngine::sync_position()
+{
+// Use audio device
+// No danger of race conditions because the output devices are closed after all
+// threads join.
+ if(do_audio)
+ {
+ return audio->current_position();
+ }
+
+ if(do_video)
+ {
+ int64_t result = timer.get_scaled_difference(
+ get_edl()->session->sample_rate);
+ return result;
+ }
+ return 0;
+}
+
+
+int RenderEngine::start_command()
+{
+ if(command->realtime && !is_nested)
+ {
+ interrupt_lock->lock("RenderEngine::start_command");
+ start_lock->lock("RenderEngine::start_command 1");
+ Thread::start();
+ start_lock->lock("RenderEngine::start_command 2");
+ start_lock->unlock();
+ }
+ return 0;
+}
+
+void RenderEngine::arm_render_threads()
+{
+ if(do_audio)
+ {
+ arender->arm_command();
+ }
+
+ if(do_video)
+ {
+ vrender->arm_command();
+ }
+}
+
+
+void RenderEngine::start_render_threads()
+{
+// Synchronization timer. Gets reset once again after the first video frame.
+ timer.update();
+
+ if(do_audio)
+ {
+ arender->start_command();
+ }
+
+ if(do_video)
+ {
+ vrender->start_command();
+ }
+}
+
+void RenderEngine::update_framerate(float framerate)
+{
+ playback_engine->mwindow->session->actual_frame_rate = framerate;
+ playback_engine->mwindow->preferences_thread->update_framerate();
+}
+
+void RenderEngine::wait_render_threads()
+{
+ if(do_audio)
+ {
+ arender->Thread::join();
+ }
+
+ if(do_video)
+ {
+ vrender->Thread::join();
+ }
+}
+
+void RenderEngine::interrupt_playback()
+{
+ interrupt_lock->lock("RenderEngine::interrupt_playback");
+ interrupted = 1;
+ if(do_audio && arender)
+ {
+ arender->interrupt_playback();
+ }
+
+ if(do_video && vrender)
+ {
+ vrender->interrupt_playback();
+ }
+ interrupt_lock->unlock();
+}
+
+int RenderEngine::close_output()
+{
+// Nested engines share devices
+ if(!is_nested)
+ {
+ if(audio)
+ {
+ audio->close_all();
+ delete audio;
+ audio = 0;
+ }
+
+
+
+ if(video)
+ {
+ video->close_all();
+ delete video;
+ video = 0;
+ }
+ }
+
+ return 0;
+}
+
+void RenderEngine::get_output_levels(double *levels, int64_t position)
+{
+ if(do_audio)
+ {
+ int history_entry = arender->get_history_number(arender->level_samples,
+ position);
+ for(int i = 0; i < MAXCHANNELS; i++)
+ {
+ if(arender->audio_out[i])
+ levels[i] = arender->level_history[i][history_entry];
+ }
+ }
+}
+
+void RenderEngine::get_module_levels(ArrayList<double> *module_levels, int64_t position)
+{
+ if(do_audio)
+ {
+ for(int i = 0; i < arender->total_modules; i++)
+ {
+//printf("RenderEngine::get_module_levels %p %p\n", ((AModule*)arender->modules[i]), ((AModule*)arender->modules[i])->level_samples);
+ int history_entry = arender->get_history_number(((AModule*)arender->modules[i])->level_samples, position);
+
+ module_levels->append(((AModule*)arender->modules[i])->level_history[history_entry]);
+ }
+ }
+}
+
+
+
+
+
+void RenderEngine::run()
+{
+ start_render_threads();
+ start_lock->unlock();
+ interrupt_lock->unlock();
+
+ wait_render_threads();
+
+ interrupt_lock->lock("RenderEngine::run");
+
+
+ if(interrupted)
+ {
+ playback_engine->tracking_position = playback_engine->get_tracking_position();
+ }
+
+ close_output();
+
+// Fix the tracking position
+ if(playback_engine)
+ {
+ if(command->command == CURRENT_FRAME)
+ {
+//printf("RenderEngine::run 4.1 %d\n", playback_engine->tracking_position);
+ playback_engine->tracking_position = command->playbackstart;
+ }
+ else
+ {
+// Make sure transport doesn't issue a pause command next
+//printf("RenderEngine::run 4.1 %d\n", playback_engine->tracking_position);
+ if(!interrupted)
+ {
+ if(do_audio)
+ playback_engine->tracking_position =
+ (double)arender->current_position /
+ command->get_edl()->session->sample_rate;
+ else
+ if(do_video)
+ {
+ playback_engine->tracking_position =
+ (double)vrender->current_position /
+ command->get_edl()->session->frame_rate;
+ }
+ }
+
+ if(!interrupted) playback_engine->command->command = STOP;
+ playback_engine->stop_tracking();
+
+ }
+ playback_engine->is_playing_back = 0;
+ }
+
+ input_lock->unlock();
+ interrupt_lock->unlock();
+}
+
+
+