/* * CINELERRA * Copyright (C) 2009-2013 Adam Williams * * 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 "asset.h" #include "bchash.h" #include "bcpbuffer.h" #include "bcsignals.h" #include "cache.h" #include "clip.h" #include "commonrender.h" #include "edits.h" #include "edl.h" #include "edlsession.h" #include "file.h" #include "filexml.h" #include "floatautos.h" #include "maskauto.h" #include "maskautos.h" #include "mwindow.h" #include "overlayframe.h" #include "patch.h" #include "pluginarray.h" #include "preferences.h" #include "renderengine.h" #include "sharedlocation.h" #include "tracks.h" #include "transition.h" #include "transportque.h" #include "units.h" #include "vattachmentpoint.h" #include "vdevicex11.h" #include "vedit.h" #include "vframe.h" #include "videodevice.h" #include "virtualvconsole.h" #include "vmodule.h" #include "vrender.h" #include "vplugin.h" #include "vtrack.h" #include #include "maskengine.h" #include "automation.h" VModule::VModule(RenderEngine *renderengine, CommonRender *commonrender, PluginArray *plugin_array, Track *track) : Module(renderengine, commonrender, plugin_array, track) { data_type = TRACK_VIDEO; overlay_temp = 0; input_temp = 0; transition_temp = 0; masker = 0; } VModule::~VModule() { if( overlay_temp ) delete overlay_temp; if( input_temp ) delete input_temp; if( transition_temp ) delete transition_temp; delete masker; } AttachmentPoint* VModule::new_attachment(Plugin *plugin) { return new VAttachmentPoint(renderengine, plugin); } int VModule::get_buffer_size() { return 1; } CICache* VModule::get_cache() { if( renderengine ) return renderengine->get_vcache(); else return cache; } int VModule::import_frame(VFrame *output, VEdit *current_edit, int64_t input_position, double frame_rate, int direction, int use_opengl) { int64_t direction_position; // Translation of edit float in_x, in_y, in_w, in_h; float out_x, out_y, out_w, out_h; int result = 0; const int debug = 0; double edl_rate = get_edl()->session->frame_rate; int64_t input_position_project = Units::to_int64(input_position * edl_rate / frame_rate + 0.001); if( !output ) printf("VModule::import_frame %d output=%p\n", __LINE__, output); //output->dump_params(); if( debug ) printf("VModule::import_frame %d this=%p input_position=%lld direction=%d\n", __LINE__, this, (long long)input_position, direction); // Convert to position corrected for direction direction_position = input_position; if( direction == PLAY_REVERSE ) { if( direction_position > 0 ) direction_position--; if( input_position_project > 0 ) input_position_project--; } if( !output ) printf("VModule::import_frame %d output=%p\n", __LINE__, output); VDeviceX11 *x11_device = 0; if( use_opengl ) { if( renderengine && renderengine->video ) { x11_device = (VDeviceX11*)renderengine->video->get_output_base(); output->set_opengl_state(VFrame::RAM); if( !x11_device ) use_opengl = 0; } } if( !output ) printf("VModule::import_frame %d output=%p x11_device=%p nested_edl=%p\n", __LINE__, output, x11_device, nested_edl); if( debug ) printf("VModule::import_frame %d current_edit=%p\n", __LINE__, current_edit); // Load frame into output // Create objects for nested EDL if( current_edit && current_edit->nested_edl ) { int command; if( debug ) printf("VModule::import_frame %d nested_edl=%p current_edit->nested_edl=%p\n", __LINE__, nested_edl, current_edit->nested_edl); // Convert requested direction to command if( renderengine->command->command == CURRENT_FRAME || renderengine->command->command == LAST_FRAME ) { command = renderengine->command->command; } else if( direction == PLAY_REVERSE ) { if( renderengine->command->single_frame() ) command = SINGLE_FRAME_REWIND; else command = NORMAL_REWIND; } else { if( renderengine->command->single_frame() ) command = SINGLE_FRAME_FWD; else command = NORMAL_FWD; } if( !nested_edl || nested_edl->id != current_edit->nested_edl->id ) { nested_edl = current_edit->nested_edl; if( nested_renderengine ) { delete nested_renderengine; nested_renderengine = 0; } if( !nested_command ) { nested_command = new TransportCommand; } if( !nested_renderengine ) { nested_command->command = command; nested_command->get_edl()->copy_all(nested_edl); nested_command->change_type = CHANGE_ALL; nested_command->realtime = renderengine->command->realtime; nested_renderengine = new RenderEngine(0, get_preferences(), 0, 1); nested_renderengine->set_vcache(get_cache()); nested_renderengine->arm_command(nested_command); } } else { // Update nested command nested_renderengine->command->command = command; nested_command->realtime = renderengine->command->realtime; } // Update nested video driver for opengl nested_renderengine->video = renderengine->video; } else { nested_edl = 0; } if( debug ) printf("VModule::import_frame %d\n", __LINE__); if( output ) { if( use_opengl ) x11_device->clear_input(output); else output->clear_frame(); } else printf("VModule::import_frame %d output=%p\n", __LINE__, output); if( current_edit && (current_edit->asset || (current_edit->nested_edl && nested_renderengine->vrender)) ) { File *file = 0; //printf("VModule::import_frame %d cache=%p\n", __LINE__, get_cache()); if( current_edit->asset ) { get_cache()->age(); file = get_cache()->check_out(current_edit->asset, get_edl()); // get_cache()->dump(); } // File found if( file || nested_edl ) { // Make all positions based on requested frame rate. int64_t edit_startproject = Units::to_int64(current_edit->startproject * frame_rate / edl_rate); int64_t edit_startsource = Units::to_int64(current_edit->startsource * frame_rate / edl_rate); // Source position going forward uint64_t position = direction_position - edit_startproject + edit_startsource; int64_t nested_position = 0; // apply speed curve to source position so the timeline agrees with the playback if( track->has_speed() ) { // integrate position from start of edit. double speed_position = edit_startsource; FloatAutos *speed_autos = (FloatAutos*)track->automation->autos[AUTOMATION_SPEED]; speed_position += speed_autos->automation_integral(edit_startproject, direction_position-edit_startproject, PLAY_FORWARD); //printf("VModule::import_frame %d %lld %lld\n", __LINE__, position, (int64_t)speed_position); position = (int64_t)speed_position; } int asset_w; int asset_h; if( debug ) printf("VModule::import_frame %d\n", __LINE__); // maybe apply speed curve here, so timeline reflects actual playback // if we hit the end of stream, freeze at last frame uint64_t max_position = 0; if( file ) { max_position = Units::to_int64((double)file->get_video_length() * frame_rate / current_edit->asset->frame_rate - 1); } else { max_position = Units::to_int64(nested_edl->tracks->total_length() * frame_rate - 1); } if( position > max_position ) position = max_position; else if( position < 0 ) position = 0; int use_cache = renderengine && renderengine->command->single_frame(); // int use_asynchronous = !use_cache && // renderengine && // Try to make rendering go faster. // But converts some formats to YUV420, which may degrade input format. //// renderengine->command->realtime && // renderengine->get_edl()->session->video_asynchronous; if( file ) { if( debug ) printf("VModule::import_frame %d\n", __LINE__); // if( use_asynchronous ) // file->start_video_decode_thread(); // else file->stop_video_thread(); int64_t normalized_position = Units::to_int64(position * current_edit->asset->frame_rate / frame_rate); // printf("VModule::import_frame %d %lld %lld\n", // __LINE__, // position, // normalized_position); file->set_layer(current_edit->channel); file->set_video_position(normalized_position, 0); asset_w = current_edit->asset->width; asset_h = current_edit->asset->height; //printf("VModule::import_frame %d normalized_position=%lld\n", __LINE__, normalized_position); } else { if( debug ) printf("VModule::import_frame %d\n", __LINE__); asset_w = nested_edl->session->output_w; asset_h = nested_edl->session->output_h; // Get source position in nested frame rate in direction of playback. nested_position = Units::to_int64(position * nested_edl->session->frame_rate / frame_rate); if( direction == PLAY_REVERSE ) nested_position++; } // Auto scale if required if( output->get_params()->get("AUTOSCALE", 0) ) { float autoscale_w = output->get_params()->get("AUTOSCALE_W", 1024); float autoscale_h = output->get_params()->get("AUTOSCALE_H", 1024); float x_scale = autoscale_w / asset_w; float y_scale = autoscale_h / asset_h; // Overriding camera in_x = 0; in_y = 0; in_w = asset_w; in_h = asset_h; if( x_scale < y_scale ) { out_w = in_w * x_scale; out_h = in_h * x_scale; } else { out_w = in_w * y_scale; out_h = in_h * y_scale; } out_x = track->track_w / 2 - out_w / 2; out_y = track->track_h / 2 - out_h / 2; } else { // Apply camera ((VTrack*)track)->calculate_input_transfer(asset_w, asset_h, input_position_project, direction, in_x, in_y, in_w, in_h, out_x, out_y, out_w, out_h); } // printf("VModule::import_frame %d %f %d %f %d\n", // __LINE__, in_w, asset_w, in_h, asset_h); // file -> temp -> output if( !EQUIV(in_x, 0) || !EQUIV(in_y, 0) || !EQUIV(in_w, track->track_w) || !EQUIV(in_h, track->track_h) || !EQUIV(out_x, 0) || !EQUIV(out_y, 0) || !EQUIV(out_w, track->track_w) || !EQUIV(out_h, track->track_h) || !EQUIV(in_w, asset_w) || !EQUIV(in_h, asset_h)) { //printf("VModule::import_frame %d file -> temp -> output\n", __LINE__); // Get temporary input buffer VFrame **input = 0; // Realtime playback if( commonrender ) { VRender *vrender = (VRender*)commonrender; //printf("VModule::import_frame %d vrender->input_temp=%p\n", __LINE__, vrender->input_temp); input = &vrender->input_temp; } else { // Menu effect input = &input_temp; } if( (*input) && ((*input)->get_w() != asset_w || (*input)->get_h() != asset_h) ) { delete (*input); (*input) = 0; } if( !(*input) ) { (*input) = new VFrame(asset_w, asset_h, get_edl()->session->color_model); } (*input)->copy_stacks(output); // file -> temp // Cache for single frame only if( file ) { if( debug ) printf("VModule::import_frame %d this=%p file=%s\n", __LINE__, this, current_edit->asset->path); if( use_cache ) file->set_cache_frames(1); result = file->read_frame((*input)); if( use_cache ) file->set_cache_frames(0); (*input)->set_opengl_state(VFrame::RAM); } else if( nested_edl ) { // If the colormodels differ, change input to nested colormodel int nested_cmodel = nested_renderengine->get_edl()->session->color_model; int current_cmodel = output->get_color_model(); int output_w = output->get_w(); int output_h = output->get_h(); VFrame *input2 = (*input); if( nested_cmodel != current_cmodel ) { // If opengl, input -> input -> output if( use_opengl ) { } else { // If software, input2 -> input -> output // Use output as a temporary. input2 = output; } if( debug ) printf("VModule::import_frame %d this=%p nested_cmodel=%d\n", __LINE__, this, nested_cmodel); input2->dump(); input2->reallocate(0, -1, 0, 0, 0, (*input)->get_w(), (*input)->get_h(), nested_cmodel, -1); input2->dump(); } if( debug ) printf("VModule::import_frame %d this=%p nested_edl=%s input2=%p\n", __LINE__, this, nested_edl->path, input2); result = nested_renderengine->vrender->process_buffer( input2, nested_position, use_opengl); if( debug ) printf("VModule::import_frame %d this=%p nested_edl=%s\n", __LINE__, this, nested_edl->path); if( nested_cmodel != current_cmodel ) { if( debug ) printf("VModule::import_frame %d\n", __LINE__); if( use_opengl ) { // Change colormodel in hardware. if( debug ) printf("VModule::import_frame %d\n", __LINE__); x11_device->convert_cmodel(input2, current_cmodel); // The converted color model is now in hardware, so return the input2 buffer // to the expected color model. input2->reallocate(0, -1, 0, 0, 0, (*input)->get_w(), (*input)->get_h(), current_cmodel, -1); } else { // Transfer from input2 to input if( debug ) printf("VModule::import_frame %d nested_cmodel=%d current_cmodel=%d input2=%p input=%p output=%p\n", __LINE__, nested_cmodel, current_cmodel, input2, (*input), output); BC_CModels::transfer((*input)->get_rows(), input2->get_rows(), 0, 0, 0, 0, 0, 0, 0, 0, input2->get_w(), input2->get_h(), 0, 0, (*input)->get_w(), (*input)->get_h(), nested_cmodel, current_cmodel, 0, input2->get_w(), (*input)->get_w()); //printf("VModule::import_frame %d\n", __LINE__); // input2 was the output buffer, so it must be restored input2->reallocate(0, -1, 0, 0, 0, output_w, output_h, current_cmodel, -1); //printf("VModule::import_frame %d\n", __LINE__); } } } // Find an overlayer object to perform the camera transformation OverlayFrame *overlayer = 0; // OpenGL playback uses hardware if( use_opengl ) { //printf("VModule::import_frame %d\n", __LINE__); } else if( commonrender ) { // Realtime playback VRender *vrender = (VRender*)commonrender; overlayer = vrender->overlayer; } else { // Menu effect if( !plugin_array ) printf("VModule::import_frame neither plugin_array nor commonrender is defined.\n"); if( !overlay_temp ) { overlay_temp = new OverlayFrame(plugin_array->mwindow->preferences->processors); } overlayer = overlay_temp; } // printf("VModule::import_frame 1 %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f\n", // in_x, in_y, in_w, in_h, out_x, out_y, out_w, out_h); // temp -> output // for( int j = 0; j < output->get_w() * 3 * 5; j++ ) // output->get_rows()[0][j] = 255; if( use_opengl ) { x11_device->do_camera(output, (*input), in_x, in_y, in_x + in_w, in_y + in_h, out_x, out_y, out_x + out_w, out_y + out_h); if( debug ) printf("VModule::import_frame %d %d %d\n", __LINE__, output->get_opengl_state(), (*input)->get_opengl_state()); } else { output->clear_frame(); // get_cache()->check_in(current_edit->asset); // return; // TRANSFER_REPLACE is the fastest transfer mode but it has the disadvantage // of producing green borders in floating point translation of YUV int mode = TRANSFER_REPLACE; if( get_edl()->session->interpolation_type != NEAREST_NEIGHBOR && BC_CModels::is_yuv(output->get_color_model()) ) mode = TRANSFER_NORMAL; if( debug ) printf("VModule::import_frame %d temp -> output\n", __LINE__); overlayer->overlay(output, (*input), in_x, in_y, in_x + in_w, in_y + in_h, out_x, out_y, out_x + out_w, out_y + out_h, 1, mode, get_edl()->session->interpolation_type); } result = 1; output->copy_stacks((*input)); //printf("VModule::import_frame %d\n", __LINE__); //(*input)->dump_params(); //output->dump_params(); } else { // file -> output if( debug ) printf("VModule::import_frame %d file -> output nested_edl=%p file=%p\n", __LINE__, nested_edl, file); if( nested_edl ) { VFrame **input = &output; // If colormodels differ, reallocate output in nested colormodel. int nested_cmodel = nested_renderengine->get_edl()->session->color_model; int current_cmodel = output->get_color_model(); if( debug ) printf("VModule::import_frame %d nested_cmodel=%d current_cmodel=%d\n", __LINE__, nested_cmodel, current_cmodel); if( nested_cmodel != current_cmodel ) { if( use_opengl ) { } else { if( commonrender ) { input = &((VRender*)commonrender)->input_temp; } else { input = &input_temp; } if( !(*input) ) (*input) = new VFrame; } (*input)->reallocate(0, -1, 0, 0, 0, output->get_w(), output->get_h(), nested_cmodel, -1); if( debug ) printf("VModule::import_frame %d\n", __LINE__); //(*input)->dump(); //(*input)->clear_frame(); } if( debug ) printf("VModule::import_frame %d %p %p\n", __LINE__, (*input)->get_rows(), (*input)); result = nested_renderengine->vrender->process_buffer( (*input), nested_position, use_opengl); if( debug ) printf("VModule::import_frame %d\n", __LINE__); // If colormodels differ, change colormodels in opengl if possible. // Swap output for temp if not possible. if( nested_cmodel != current_cmodel ) { if( use_opengl ) { x11_device->convert_cmodel(output, current_cmodel); // The color model was changed in place, so return output buffer output->reallocate(0, -1, 0, 0, 0, output->get_w(), output->get_h(), current_cmodel, -1); } else { // Transfer from temporary to output if( debug ) printf("VModule::import_frame %d %d %d %d %d %d %d\n", __LINE__, (*input)->get_w(), (*input)->get_h(), output->get_w(), output->get_h(), nested_cmodel, current_cmodel); BC_CModels::transfer(output->get_rows(), (*input)->get_rows(), 0, 0, 0, 0, 0, 0, 0, 0, (*input)->get_w(), (*input)->get_h(), 0, 0, output->get_w(), output->get_h(), nested_cmodel, current_cmodel, 0, (*input)->get_w(), output->get_w()); } } } else if( file ) { // Cache single frames //memset(output->get_rows()[0], 0xff, 1024); if( use_cache ) file->set_cache_frames(1); result = file->read_frame(output); if( use_cache ) file->set_cache_frames(0); output->set_opengl_state(VFrame::RAM); } } if( file ) { get_cache()->check_in(current_edit->asset); file = 0; } } else { // Source not found result = 1; } // printf("VModule::import_frame %d cache=%p\n", // __LINE__, // get_cache()); } else { // Source is silence if( debug ) printf("VModule::import_frame %d\n", __LINE__); if( use_opengl ) { x11_device->clear_input(output); } else { output->clear_frame(); } } if( debug ) printf("VModule::import_frame %d done\n", __LINE__); return result; } int VModule::render(VFrame *output, int64_t start_position, int direction, double frame_rate, int use_nudge, int debug_render, int use_opengl) { int result = 0; double edl_rate = get_edl()->session->frame_rate; //printf("VModule::render %d %ld\n", __LINE__, start_position); if( use_nudge ) start_position += Units::to_int64(track->nudge * frame_rate / edl_rate); int64_t start_position_project = Units::to_int64(start_position * edl_rate / frame_rate + 0.5); update_transition(start_position_project, direction); VEdit* current_edit = (VEdit*)track->edits->editof(start_position_project, direction, 0); VEdit* previous_edit = 0; //printf("VModule::render %d %p %ld %d\n", __LINE__, current_edit, start_position_project, direction); Plugin *transition = get_edl()->tracks->plugin_exists(transition_id); if( debug_render ) printf(" VModule::render %d %d %jd %s transition=%p opengl=%d current_edit=%p output=%p\n", __LINE__, use_nudge, start_position_project, track->title, transition, use_opengl, current_edit, output); if( !current_edit ) { output->clear_frame(); // We do not apply mask here, since alpha is 0, and neither substracting nor multypling changes it // Another mask mode - "addition" should be added to be able to create mask from empty frames // in this case we would call masking here too... return 0; } // Process transition if( transition && transition->on ) { // Get temporary buffer VFrame **transition_input = 0; if( commonrender ) { VRender *vrender = (VRender*)commonrender; transition_input = &vrender->transition_temp; } else { transition_input = &transition_temp; } if( (*transition_input) && ((*transition_input)->get_w() != track->track_w || (*transition_input)->get_h() != track->track_h) ) { delete (*transition_input); (*transition_input) = 0; } // Load incoming frame if( !(*transition_input) ) { (*transition_input) = new VFrame(track->track_w, track->track_h, get_edl()->session->color_model); } (*transition_input)->copy_stacks(output); //printf("VModule::render %d\n", __LINE__); result = import_frame((*transition_input), current_edit, start_position, frame_rate, direction, use_opengl); // Load transition buffer previous_edit = (VEdit*)current_edit->previous; result |= import_frame(output, previous_edit, start_position, frame_rate, direction, use_opengl); //printf("VModule::render %d %p %p %p %p\n", __LINE__, // (*transition_input), (*transition_input)->get_pbuffer(), // output, output->get_pbuffer()); // Execute plugin with transition_input and output here if( renderengine ) transition_server->set_use_opengl(use_opengl, renderengine->video); transition_server->process_transition((*transition_input), output, (direction == PLAY_FORWARD) ? (start_position_project - current_edit->startproject) : (start_position_project - current_edit->startproject - 1), transition->length); } else { // Load output buffer result = import_frame(output, current_edit, start_position, frame_rate, direction, use_opengl); } Auto *current = 0; MaskAutos *keyframe_set = (MaskAutos*)track->automation->autos[AUTOMATION_MASK]; int64_t mask_position = !renderengine ? start_position : renderengine->vrender->current_position; MaskAuto *keyframe = (MaskAuto*)keyframe_set->get_prev_auto(mask_position, direction, current); if( keyframe->apply_before_plugins && keyframe->has_active_mask() ) { VDeviceX11 *x11_device = 0; if( use_opengl && renderengine && renderengine->video ) { x11_device = (VDeviceX11*)renderengine->video->get_output_base(); if( !x11_device->can_mask(mask_position, keyframe_set) ) { if( output->get_opengl_state() != VFrame::RAM ) { int w = output->get_w(), h = output->get_h(); x11_device->do_camera(output, output, 0,0,w,h, 0,0,w,h); // copy to ram } use_opengl = 0; } } if( use_opengl && x11_device ) { x11_device->do_mask(output, mask_position, keyframe_set, keyframe, keyframe); } else { if( !masker ) { int cpus = renderengine ? renderengine->preferences->processors : plugin_array->mwindow->preferences->processors; masker = new MaskEngine(cpus); } masker->do_mask(output, mask_position, keyframe_set, keyframe, keyframe); } } return result; } void VModule::create_objects() { Module::create_objects(); }