Path: blob/master/servers/movie_writer/movie_writer.cpp
10278 views
/**************************************************************************/1/* movie_writer.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "movie_writer.h"31#include "core/config/project_settings.h"32#include "core/io/dir_access.h"33#include "core/os/time.h"34#include "scene/main/window.h"35#include "servers/audio/audio_driver_dummy.h"36#include "servers/display_server.h"37#include "servers/rendering_server.h"3839MovieWriter *MovieWriter::writers[MovieWriter::MAX_WRITERS];40uint32_t MovieWriter::writer_count = 0;4142void MovieWriter::add_writer(MovieWriter *p_writer) {43ERR_FAIL_COND(writer_count == MAX_WRITERS);44writers[writer_count++] = p_writer;45}4647MovieWriter *MovieWriter::find_writer_for_file(const String &p_file) {48for (int32_t i = writer_count - 1; i >= 0; i--) { // More recent last, to have override ability.49if (writers[i]->handles_file(p_file)) {50return writers[i];51}52}53return nullptr;54}5556uint32_t MovieWriter::get_audio_mix_rate() const {57uint32_t ret = 48000;58GDVIRTUAL_CALL(_get_audio_mix_rate, ret);59return ret;60}61AudioServer::SpeakerMode MovieWriter::get_audio_speaker_mode() const {62AudioServer::SpeakerMode ret = AudioServer::SPEAKER_MODE_STEREO;63GDVIRTUAL_CALL(_get_audio_speaker_mode, ret);64return ret;65}6667Error MovieWriter::write_begin(const Size2i &p_movie_size, uint32_t p_fps, const String &p_base_path) {68Error ret = ERR_UNCONFIGURED;69GDVIRTUAL_CALL(_write_begin, p_movie_size, p_fps, p_base_path, ret);70return ret;71}7273Error MovieWriter::write_frame(const Ref<Image> &p_image, const int32_t *p_audio_data) {74Error ret = ERR_UNCONFIGURED;75GDVIRTUAL_CALL(_write_frame, p_image, p_audio_data, ret);76return ret;77}7879void MovieWriter::write_end() {80GDVIRTUAL_CALL(_write_end);81}8283bool MovieWriter::handles_file(const String &p_path) const {84bool ret = false;85GDVIRTUAL_CALL(_handles_file, p_path, ret);86return ret;87}8889void MovieWriter::get_supported_extensions(List<String> *r_extensions) const {90Vector<String> exts;91GDVIRTUAL_CALL(_get_supported_extensions, exts);92for (int i = 0; i < exts.size(); i++) {93r_extensions->push_back(exts[i]);94}95}9697void MovieWriter::begin(const Size2i &p_movie_size, uint32_t p_fps, const String &p_base_path) {98project_name = GLOBAL_GET("application/config/name");99100print_line(vformat("Movie Maker mode enabled, recording movie at %d FPS...", p_fps));101102// When using Display/Window/Stretch/Mode = Viewport, use the project's103// configured viewport size instead of the size of the window in the OS104Size2i actual_movie_size = p_movie_size;105String stretch_mode = GLOBAL_GET("display/window/stretch/mode");106if (stretch_mode == "viewport") {107actual_movie_size.width = GLOBAL_GET("display/window/size/viewport_width");108actual_movie_size.height = GLOBAL_GET("display/window/size/viewport_height");109110print_line(vformat("Movie Maker mode using project viewport size: %dx%d",111actual_movie_size.width, actual_movie_size.height));112}113114// Check for available disk space and warn the user if needed.115Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);116String path = p_base_path.get_basename();117if (path.is_relative_path()) {118path = "res://" + path;119}120dir->open(path);121if (dir->get_space_left() < 10 * Math::pow(1024.0, 3.0)) {122// Less than 10 GiB available.123WARN_PRINT(vformat("Current available space on disk is low (%s). MovieWriter will fail during movie recording if the disk runs out of available space.", String::humanize_size(dir->get_space_left())));124}125126cpu_time = 0.0f;127gpu_time = 0.0f;128encoding_time_usec = 0;129130mix_rate = get_audio_mix_rate();131AudioDriverDummy::get_dummy_singleton()->set_mix_rate(mix_rate);132AudioDriverDummy::get_dummy_singleton()->set_speaker_mode(AudioDriver::SpeakerMode(get_audio_speaker_mode()));133fps = p_fps;134if ((mix_rate % fps) != 0) {135WARN_PRINT("MovieWriter's audio mix rate (" + itos(mix_rate) + ") can not be divided by the recording FPS (" + itos(fps) + "). Audio may go out of sync over time.");136}137138audio_channels = AudioDriverDummy::get_dummy_singleton()->get_channels();139audio_mix_buffer.resize(mix_rate * audio_channels / fps);140141write_begin(actual_movie_size, p_fps, p_base_path);142}143144void MovieWriter::_bind_methods() {145ClassDB::bind_static_method("MovieWriter", D_METHOD("add_writer", "writer"), &MovieWriter::add_writer);146147GDVIRTUAL_BIND(_get_audio_mix_rate)148GDVIRTUAL_BIND(_get_audio_speaker_mode)149150GDVIRTUAL_BIND(_handles_file, "path")151152GDVIRTUAL_BIND(_write_begin, "movie_size", "fps", "base_path")153GDVIRTUAL_BIND(_write_frame, "frame_image", "audio_frame_block")154GDVIRTUAL_BIND(_write_end)155156GLOBAL_DEF(PropertyInfo(Variant::INT, "editor/movie_writer/mix_rate", PROPERTY_HINT_RANGE, "8000,192000,1,suffix:Hz"), 48000);157GLOBAL_DEF(PropertyInfo(Variant::INT, "editor/movie_writer/speaker_mode", PROPERTY_HINT_ENUM, "Stereo,3.1,5.1,7.1"), 0);158GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "editor/movie_writer/video_quality", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), 0.75);159GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "editor/movie_writer/ogv/audio_quality", PROPERTY_HINT_RANGE, "-0.1,1.0,0.01"), 0.5);160GLOBAL_DEF(PropertyInfo(Variant::INT, "editor/movie_writer/ogv/encoding_speed", PROPERTY_HINT_ENUM, "Fastest (Lowest Efficiency):4,Fast (Low Efficiency):3,Slow (High Efficiency):2,Slowest (Highest Efficiency):1"), 4);161GLOBAL_DEF(PropertyInfo(Variant::INT, "editor/movie_writer/ogv/keyframe_interval", PROPERTY_HINT_RANGE, "1,1024,1"), 64);162163// Used by the editor.164GLOBAL_DEF_BASIC("editor/movie_writer/movie_file", "");165GLOBAL_DEF_BASIC("editor/movie_writer/disable_vsync", false);166GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "editor/movie_writer/fps", PROPERTY_HINT_RANGE, "1,300,1,suffix:FPS"), 60);167}168169void MovieWriter::set_extensions_hint() {170RBSet<String> found;171for (uint32_t i = 0; i < writer_count; i++) {172List<String> extensions;173writers[i]->get_supported_extensions(&extensions);174for (const String &ext : extensions) {175found.insert(ext);176}177}178179String ext_hint;180181for (const String &S : found) {182if (ext_hint != "") {183ext_hint += ",";184}185ext_hint += "*." + S;186}187ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, "editor/movie_writer/movie_file", PROPERTY_HINT_GLOBAL_SAVE_FILE, ext_hint));188}189190void MovieWriter::add_frame() {191const int movie_time_seconds = Engine::get_singleton()->get_frames_drawn() / fps;192const int frame_remainder = Engine::get_singleton()->get_frames_drawn() % fps;193const String movie_time = vformat("%s:%s:%s:%s",194String::num(movie_time_seconds / 3600, 0).pad_zeros(2),195String::num((movie_time_seconds % 3600) / 60, 0).pad_zeros(2),196String::num(movie_time_seconds % 60, 0).pad_zeros(2),197String::num(frame_remainder, 0).pad_zeros(2));198199Window *main_window = Window::get_from_id(DisplayServer::MAIN_WINDOW_ID);200if (main_window) {201main_window->set_title(vformat("MovieWriter: Frame %d (time: %s) - %s", Engine::get_singleton()->get_frames_drawn(), movie_time, project_name));202}203204RID main_vp_rid = RenderingServer::get_singleton()->viewport_find_from_screen_attachment(DisplayServer::MAIN_WINDOW_ID);205RID main_vp_texture = RenderingServer::get_singleton()->viewport_get_texture(main_vp_rid);206Ref<Image> vp_tex = RenderingServer::get_singleton()->texture_2d_get(main_vp_texture);207if (RenderingServer::get_singleton()->viewport_is_using_hdr_2d(main_vp_rid)) {208vp_tex->convert(Image::FORMAT_RGBA8);209vp_tex->linear_to_srgb();210}211212RenderingServer::get_singleton()->viewport_set_measure_render_time(main_vp_rid, true);213cpu_time += RenderingServer::get_singleton()->viewport_get_measured_render_time_cpu(main_vp_rid);214cpu_time += RenderingServer::get_singleton()->get_frame_setup_time_cpu();215gpu_time += RenderingServer::get_singleton()->viewport_get_measured_render_time_gpu(main_vp_rid);216217AudioDriverDummy::get_dummy_singleton()->mix_audio(mix_rate / fps, audio_mix_buffer.ptr());218219uint64_t encoding_start_usec = Time::get_singleton()->get_ticks_usec();220write_frame(vp_tex, audio_mix_buffer.ptr());221uint64_t encoding_end_usec = Time::get_singleton()->get_ticks_usec();222encoding_time_usec += encoding_end_usec - encoding_start_usec;223}224225void MovieWriter::end() {226uint64_t encoding_start_usec = Time::get_singleton()->get_ticks_usec();227write_end();228uint64_t encoding_end_usec = Time::get_singleton()->get_ticks_usec();229encoding_time_usec += encoding_end_usec - encoding_start_usec;230231// Print a report with various statistics.232print_line("--------------------------------------------------------------------------------");233String movie_path = Engine::get_singleton()->get_write_movie_path();234if (movie_path.is_relative_path()) {235// Print absolute path to make finding the file easier,236// and to make it clickable in terminal emulators that support this.237movie_path = ProjectSettings::get_singleton()->globalize_path("res://").path_join(movie_path);238}239print_line(vformat("Done recording movie at path: %s", movie_path));240241const int movie_time_seconds = Engine::get_singleton()->get_frames_drawn() / fps;242const int frame_remainder = Engine::get_singleton()->get_frames_drawn() % fps;243const String movie_time = vformat("%s:%s:%s:%s",244String::num(movie_time_seconds / 3600, 0).pad_zeros(2),245String::num((movie_time_seconds % 3600) / 60, 0).pad_zeros(2),246String::num(movie_time_seconds % 60, 0).pad_zeros(2),247String::num(frame_remainder, 0).pad_zeros(2));248249const int real_time_seconds = Time::get_singleton()->get_ticks_msec() / 1000;250const String real_time = vformat("%s:%s:%s",251String::num(real_time_seconds / 3600, 0).pad_zeros(2),252String::num((real_time_seconds % 3600) / 60, 0).pad_zeros(2),253String::num(real_time_seconds % 60, 0).pad_zeros(2));254255print_line(vformat("%d frames at %d FPS (movie length: %s), recorded in %s (%d%% of real-time speed).", Engine::get_singleton()->get_frames_drawn(), fps, movie_time, real_time, (float(MAX(1, movie_time_seconds)) / MAX(1, real_time_seconds)) * 100));256print_line(vformat("CPU render time: %.2f seconds (average: %.2f ms/frame)", cpu_time / 1000, cpu_time / Engine::get_singleton()->get_frames_drawn()));257print_line(vformat("GPU render time: %.2f seconds (average: %.2f ms/frame)", gpu_time / 1000, gpu_time / Engine::get_singleton()->get_frames_drawn()));258print_line(vformat("Encoding time: %.2f seconds (average: %.2f ms/frame)", encoding_time_usec / 1000000.f, encoding_time_usec / 1000.f / Engine::get_singleton()->get_frames_drawn()));259print_line("--------------------------------------------------------------------------------");260}261262263