diff options
Diffstat (limited to 'servers')
-rw-r--r-- | servers/SCsub | 1 | ||||
-rw-r--r-- | servers/audio/audio_driver_dummy.cpp | 67 | ||||
-rw-r--r-- | servers/audio/audio_driver_dummy.h | 22 | ||||
-rw-r--r-- | servers/movie_writer/SCsub | 5 | ||||
-rw-r--r-- | servers/movie_writer/movie_writer.cpp | 306 | ||||
-rw-r--r-- | servers/movie_writer/movie_writer.h | 123 | ||||
-rw-r--r-- | servers/movie_writer/movie_writer_mjpeg.cpp | 263 | ||||
-rw-r--r-- | servers/movie_writer/movie_writer_mjpeg.h | 73 | ||||
-rw-r--r-- | servers/register_server_types.cpp | 15 | ||||
-rw-r--r-- | servers/rendering/renderer_viewport.cpp | 14 | ||||
-rw-r--r-- | servers/rendering/renderer_viewport.h | 2 | ||||
-rw-r--r-- | servers/rendering/rendering_server_default.h | 1 | ||||
-rw-r--r-- | servers/rendering_server.h | 2 |
13 files changed, 880 insertions, 14 deletions
diff --git a/servers/SCsub b/servers/SCsub index 66a1b9b26f..2ce90e970b 100644 --- a/servers/SCsub +++ b/servers/SCsub @@ -14,6 +14,7 @@ SConscript("audio/SCsub") SConscript("text/SCsub") SConscript("debugger/SCsub") SConscript("extensions/SCsub") +SConscript("movie_writer/SCsub") lib = env.add_library("servers", env.servers_sources) diff --git a/servers/audio/audio_driver_dummy.cpp b/servers/audio/audio_driver_dummy.cpp index 635e4601c7..60eb657923 100644 --- a/servers/audio/audio_driver_dummy.cpp +++ b/servers/audio/audio_driver_dummy.cpp @@ -33,22 +33,24 @@ #include "core/config/project_settings.h" #include "core/os/os.h" +AudioDriverDummy *AudioDriverDummy::singleton = nullptr; + Error AudioDriverDummy::init() { active = false; thread_exited = false; exit_thread = false; samples_in = nullptr; - mix_rate = GLOBAL_GET("audio/driver/mix_rate"); - speaker_mode = SPEAKER_MODE_STEREO; - channels = 2; - - int latency = GLOBAL_GET("audio/driver/output_latency"); - buffer_frames = closest_power_of_2(latency * mix_rate / 1000); + if (mix_rate == -1) { + mix_rate = GLOBAL_GET("audio/driver/mix_rate"); + } + channels = get_channels(); samples_in = memnew_arr(int32_t, (size_t)buffer_frames * channels); - thread.start(AudioDriverDummy::thread_func, this); + if (use_threads) { + thread.start(AudioDriverDummy::thread_func, this); + } return OK; }; @@ -93,11 +95,56 @@ void AudioDriverDummy::unlock() { mutex.unlock(); }; +void AudioDriverDummy::set_use_threads(bool p_use_threads) { + use_threads = p_use_threads; +} + +void AudioDriverDummy::set_speaker_mode(SpeakerMode p_mode) { + speaker_mode = p_mode; +} + +void AudioDriverDummy::set_mix_rate(int p_rate) { + mix_rate = p_rate; +} + +uint32_t AudioDriverDummy::get_channels() const { + static const int channels_for_mode[4] = { 2, 4, 8, 16 }; + return channels_for_mode[speaker_mode]; +} + +void AudioDriverDummy::mix_audio(int p_frames, int32_t *p_buffer) { + ERR_FAIL_COND(!active); // If not active, should not mix. + ERR_FAIL_COND(use_threads == true); // If using threads, this will not work well. + + uint32_t todo = p_frames; + while (todo) { + uint32_t to_mix = MIN(buffer_frames, todo); + lock(); + audio_server_process(to_mix, samples_in); + unlock(); + + uint32_t total_samples = to_mix * channels; + + for (uint32_t i = 0; i < total_samples; i++) { + p_buffer[i] = samples_in[i]; + } + + todo -= to_mix; + p_buffer += total_samples; + } +} + void AudioDriverDummy::finish() { - exit_thread = true; - thread.wait_to_finish(); + if (use_threads) { + exit_thread = true; + thread.wait_to_finish(); + } if (samples_in) { memdelete_arr(samples_in); }; -}; +} + +AudioDriverDummy::AudioDriverDummy() { + singleton = this; +} diff --git a/servers/audio/audio_driver_dummy.h b/servers/audio/audio_driver_dummy.h index 68b523e086..232a8d5e1f 100644 --- a/servers/audio/audio_driver_dummy.h +++ b/servers/audio/audio_driver_dummy.h @@ -44,9 +44,9 @@ class AudioDriverDummy : public AudioDriver { static void thread_func(void *p_udata); - unsigned int buffer_frames; - unsigned int mix_rate; - SpeakerMode speaker_mode; + uint32_t buffer_frames = 4096; + int32_t mix_rate = -1; + SpeakerMode speaker_mode = SPEAKER_MODE_STEREO; int channels; @@ -54,6 +54,10 @@ class AudioDriverDummy : public AudioDriver { bool thread_exited; mutable bool exit_thread; + bool use_threads = true; + + static AudioDriverDummy *singleton; + public: const char *get_name() const { return "Dummy"; @@ -67,7 +71,17 @@ public: virtual void unlock(); virtual void finish(); - AudioDriverDummy() {} + void set_use_threads(bool p_use_threads); + void set_speaker_mode(SpeakerMode p_mode); + void set_mix_rate(int p_rate); + + uint32_t get_channels() const; + + void mix_audio(int p_frames, int32_t *p_buffer); + + static AudioDriverDummy *get_dummy_singleton() { return singleton; } + + AudioDriverDummy(); ~AudioDriverDummy() {} }; diff --git a/servers/movie_writer/SCsub b/servers/movie_writer/SCsub new file mode 100644 index 0000000000..86681f9c74 --- /dev/null +++ b/servers/movie_writer/SCsub @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +Import("env") + +env.add_source_files(env.servers_sources, "*.cpp") diff --git a/servers/movie_writer/movie_writer.cpp b/servers/movie_writer/movie_writer.cpp new file mode 100644 index 0000000000..ac60dc3b9a --- /dev/null +++ b/servers/movie_writer/movie_writer.cpp @@ -0,0 +1,306 @@ +/*************************************************************************/ +/* movie_writer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "movie_writer.h" +#include "core/config/project_settings.h" +#include "core/io/dir_access.h" + +MovieWriter *MovieWriter::writers[MovieWriter::MAX_WRITERS]; +uint32_t MovieWriter::writer_count = 0; + +void MovieWriter::add_writer(MovieWriter *p_writer) { + ERR_FAIL_COND(writer_count == MAX_WRITERS); + writers[writer_count++] = p_writer; +} + +MovieWriter *MovieWriter::find_writer_for_file(const String &p_file) { + for (int32_t i = writer_count - 1; i >= 0; i--) { // More recent last, to have override ability. + if (writers[i]->handles_file(p_file)) { + return writers[i]; + } + } + return nullptr; +} + +uint32_t MovieWriter::get_audio_mix_rate() const { + uint32_t ret = 0; + if (GDVIRTUAL_REQUIRED_CALL(_get_audio_mix_rate, ret)) { + return ret; + } + return 48000; +} +AudioServer::SpeakerMode MovieWriter::get_audio_speaker_mode() const { + AudioServer::SpeakerMode ret = AudioServer::SPEAKER_MODE_STEREO; + if (GDVIRTUAL_REQUIRED_CALL(_get_audio_speaker_mode, ret)) { + return ret; + } + return AudioServer::SPEAKER_MODE_STEREO; +} + +Error MovieWriter::write_begin(const Size2i &p_movie_size, uint32_t p_fps, const String &p_base_path) { + Error ret = OK; + if (GDVIRTUAL_REQUIRED_CALL(_write_begin, p_movie_size, p_fps, p_base_path, ret)) { + return ret; + } + return ERR_UNCONFIGURED; +} + +Error MovieWriter::write_frame(const Ref<Image> &p_image, const int32_t *p_audio_data) { + Error ret = OK; + if (GDVIRTUAL_REQUIRED_CALL(_write_frame, p_image, p_audio_data, ret)) { + return ret; + } + return ERR_UNCONFIGURED; +} + +void MovieWriter::write_end() { + GDVIRTUAL_REQUIRED_CALL(_write_end); +} + +bool MovieWriter::handles_file(const String &p_path) const { + bool ret = false; + if (GDVIRTUAL_REQUIRED_CALL(_handles_file, p_path, ret)) { + return ret; + } + return false; +} + +void MovieWriter::get_supported_extensions(List<String> *r_extensions) const { + Vector<String> exts; + if (GDVIRTUAL_REQUIRED_CALL(_get_supported_extensions, exts)) { + for (int i = 0; i < exts.size(); i++) { + r_extensions->push_back(exts[i]); + } + } +} + +void MovieWriter::begin(const Size2i &p_movie_size, uint32_t p_fps, const String &p_base_path) { + mix_rate = get_audio_mix_rate(); + AudioDriverDummy::get_dummy_singleton()->set_mix_rate(mix_rate); + AudioDriverDummy::get_dummy_singleton()->set_speaker_mode(AudioDriver::SpeakerMode(get_audio_speaker_mode())); + fps = p_fps; + if ((mix_rate % fps) != 0) { + WARN_PRINT("Audio mix rate (" + itos(mix_rate) + ") can not be divided by fps (" + itos(fps) + "). Audio may go out of sync over time."); + } + + audio_channels = AudioDriverDummy::get_dummy_singleton()->get_channels(); + audio_mix_buffer.resize(mix_rate * audio_channels / fps); + + write_begin(p_movie_size, p_fps, p_base_path); +} + +void MovieWriter::_bind_methods() { + ClassDB::bind_static_method("MovieWriter", D_METHOD("add_writer", "writer"), &MovieWriter::add_writer); + + GDVIRTUAL_BIND(_get_audio_mix_rate) + GDVIRTUAL_BIND(_get_audio_speaker_mode) + + GDVIRTUAL_BIND(_handles_file, "path") + + GDVIRTUAL_BIND(_write_begin, "movie_size", "fps", "base_path") + GDVIRTUAL_BIND(_write_frame, "frame_image", "audio_frame_block") + GDVIRTUAL_BIND(_write_end) + + GLOBAL_DEF("editor/movie_writer/mix_rate_hz", 48000); + GLOBAL_DEF("editor/movie_writer/speaker_mode", 0); + ProjectSettings::get_singleton()->set_custom_property_info("editor/movie_writer/speaker_mode", PropertyInfo(Variant::INT, "editor/movie_writer/speaker_mode", PROPERTY_HINT_ENUM, "Stereo,3.1,5.1,7.1")); + GLOBAL_DEF("editor/movie_writer/mjpeg_quality", 0.75); + // used by the editor + GLOBAL_DEF_BASIC("editor/movie_writer/movie_file", ""); + GLOBAL_DEF_BASIC("editor/movie_writer/disable_vsync", false); + GLOBAL_DEF_BASIC("editor/movie_writer/fps", 60); + ProjectSettings::get_singleton()->set_custom_property_info("editor/movie_writer/fps", PropertyInfo(Variant::INT, "editor/movie_writer/fps", PROPERTY_HINT_RANGE, "1,300,1")); +} + +void MovieWriter::set_extensions_hint() { + RBSet<String> found; + for (uint32_t i = 0; i < writer_count; i++) { + List<String> extensions; + writers[i]->get_supported_extensions(&extensions); + for (const String &ext : extensions) { + found.insert(ext); + } + } + + String ext_hint; + + for (const String &S : found) { + if (ext_hint != "") { + ext_hint += ","; + } + ext_hint += "*." + S; + } + ProjectSettings::get_singleton()->set_custom_property_info("editor/movie_writer/movie_file", PropertyInfo(Variant::STRING, "editor/movie_writer/movie_file", PROPERTY_HINT_GLOBAL_SAVE_FILE, ext_hint)); +} + +void MovieWriter::add_frame(const Ref<Image> &p_image) { + AudioDriverDummy::get_dummy_singleton()->mix_audio(mix_rate / fps, audio_mix_buffer.ptr()); + write_frame(p_image, audio_mix_buffer.ptr()); +} + +void MovieWriter::end() { + write_end(); +} +///////////////////////////////////////// + +uint32_t MovieWriterPNGWAV::get_audio_mix_rate() const { + return mix_rate; +} +AudioServer::SpeakerMode MovieWriterPNGWAV::get_audio_speaker_mode() const { + return speaker_mode; +} + +void MovieWriterPNGWAV::get_supported_extensions(List<String> *r_extensions) const { + r_extensions->push_back("png"); +} + +bool MovieWriterPNGWAV::handles_file(const String &p_path) const { + return p_path.get_extension().to_lower() == "png"; +} + +String MovieWriterPNGWAV::zeros_str(uint32_t p_index) { + char zeros[MAX_TRAILING_ZEROS + 1]; + for (uint32_t i = 0; i < MAX_TRAILING_ZEROS; i++) { + uint32_t idx = MAX_TRAILING_ZEROS - i - 1; + uint32_t digit = (p_index / uint32_t(Math::pow(double(10), double(idx)))) % 10; + zeros[i] = '0' + digit; + } + zeros[MAX_TRAILING_ZEROS] = 0; + return zeros; +} + +Error MovieWriterPNGWAV::write_begin(const Size2i &p_movie_size, uint32_t p_fps, const String &p_base_path) { + // Quick & Dirty PNGWAV Code based on - https://docs.microsoft.com/en-us/windows/win32/directshow/avi-riff-file-reference + + base_path = p_base_path.get_basename(); + if (base_path.is_relative_path()) { + base_path = "res://" + base_path; + } + + { + //Remove existing files before writing anew + uint32_t idx = 0; + Ref<DirAccess> d = DirAccess::open(base_path.get_base_dir()); + String file = base_path.get_file(); + while (true) { + String path = file + zeros_str(idx) + ".png"; + if (d->remove(path) != OK) { + break; + } + } + } + + f_wav = FileAccess::open(base_path + ".wav", FileAccess::WRITE_READ); + ERR_FAIL_COND_V(f_wav.is_null(), ERR_CANT_OPEN); + + fps = p_fps; + + f_wav->store_buffer((const uint8_t *)"RIFF", 4); + int total_size = 4 /* WAVE */ + 8 /* fmt+size */ + 16 /* format */ + 8 /* data+size */; + f_wav->store_32(total_size); //will store final later + f_wav->store_buffer((const uint8_t *)"WAVE", 4); + + /* FORMAT CHUNK */ + + f_wav->store_buffer((const uint8_t *)"fmt ", 4); + + uint32_t channels = 2; + switch (speaker_mode) { + case AudioServer::SPEAKER_MODE_STEREO: + channels = 2; + break; + case AudioServer::SPEAKER_SURROUND_31: + channels = 4; + break; + case AudioServer::SPEAKER_SURROUND_51: + channels = 6; + break; + case AudioServer::SPEAKER_SURROUND_71: + channels = 8; + break; + } + + f_wav->store_32(16); //standard format, no extra fields + f_wav->store_16(1); // compression code, standard PCM + f_wav->store_16(channels); //CHANNELS: 2 + + f_wav->store_32(mix_rate); + + /* useless stuff the format asks for */ + + int bits_per_sample = 32; + int blockalign = bits_per_sample / 8 * channels; + int bytes_per_sec = mix_rate * blockalign; + + audio_block_size = (mix_rate / fps) * blockalign; + + f_wav->store_32(bytes_per_sec); + f_wav->store_16(blockalign); // block align (unused) + f_wav->store_16(bits_per_sample); + + /* DATA CHUNK */ + + f_wav->store_buffer((const uint8_t *)"data", 4); + + f_wav->store_32(0); //data size... wooh + wav_data_size_pos = f_wav->get_position(); + + return OK; +} + +Error MovieWriterPNGWAV::write_frame(const Ref<Image> &p_image, const int32_t *p_audio_data) { + ERR_FAIL_COND_V(!f_wav.is_valid(), ERR_UNCONFIGURED); + + Vector<uint8_t> png_buffer = p_image->save_png_to_buffer(); + + Ref<FileAccess> fi = FileAccess::open(base_path + zeros_str(frame_count) + ".png", FileAccess::WRITE); + fi->store_buffer(png_buffer.ptr(), png_buffer.size()); + f_wav->store_buffer((const uint8_t *)p_audio_data, audio_block_size); + + frame_count++; + + return OK; +} + +void MovieWriterPNGWAV::write_end() { + if (f_wav.is_valid()) { + uint32_t total_size = 4 /* WAVE */ + 8 /* fmt+size */ + 16 /* format */ + 8 /* data+size */; + uint32_t datasize = f_wav->get_position() - wav_data_size_pos; + f_wav->seek(4); + f_wav->store_32(total_size + datasize); + f_wav->seek(0x28); + f_wav->store_32(datasize); + } +} + +MovieWriterPNGWAV::MovieWriterPNGWAV() { + mix_rate = GLOBAL_GET("editor/movie_writer/mix_rate_hz"); + speaker_mode = AudioServer::SpeakerMode(int(GLOBAL_GET("editor/movie_writer/speaker_mode"))); +} diff --git a/servers/movie_writer/movie_writer.h b/servers/movie_writer/movie_writer.h new file mode 100644 index 0000000000..11e739df39 --- /dev/null +++ b/servers/movie_writer/movie_writer.h @@ -0,0 +1,123 @@ +/*************************************************************************/ +/* movie_writer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef MOVIE_WRITER_H +#define MOVIE_WRITER_H + +#include "core/templates/local_vector.h" +#include "servers/audio/audio_driver_dummy.h" +#include "servers/audio_server.h" + +class MovieWriter : public Object { + GDCLASS(MovieWriter, Object); + + uint64_t fps = 0; + uint64_t mix_rate = 0; + uint32_t audio_channels = 0; + + LocalVector<int32_t> audio_mix_buffer; + + enum { + MAX_WRITERS = 8 + }; + static MovieWriter *writers[]; + static uint32_t writer_count; + +protected: + virtual uint32_t get_audio_mix_rate() const; + virtual AudioServer::SpeakerMode get_audio_speaker_mode() const; + + virtual Error write_begin(const Size2i &p_movie_size, uint32_t p_fps, const String &p_base_path); + virtual Error write_frame(const Ref<Image> &p_image, const int32_t *p_audio_data); + virtual void write_end(); + + GDVIRTUAL0RC(uint32_t, _get_audio_mix_rate) + GDVIRTUAL0RC(AudioServer::SpeakerMode, _get_audio_speaker_mode) + + GDVIRTUAL1RC(bool, _handles_file, const String &) + GDVIRTUAL0RC(Vector<String>, _get_supported_extensions) + + GDVIRTUAL3R(Error, _write_begin, const Size2i &, uint32_t, const String &) + GDVIRTUAL2R(Error, _write_frame, const Ref<Image> &, GDNativeConstPtr<int32_t>) + GDVIRTUAL0(_write_end) + + static void _bind_methods(); + +public: + virtual bool handles_file(const String &p_path) const; + virtual void get_supported_extensions(List<String> *r_extensions) const; + + static void add_writer(MovieWriter *p_writer); + static MovieWriter *find_writer_for_file(const String &p_file); + + void begin(const Size2i &p_movie_size, uint32_t p_fps, const String &p_base_path); + void add_frame(const Ref<Image> &p_image); + + static void set_extensions_hint(); + + void end(); +}; + +class MovieWriterPNGWAV : public MovieWriter { + GDCLASS(MovieWriterPNGWAV, MovieWriter) + + enum { + MAX_TRAILING_ZEROS = 8 // more than 10 days at 60fps, no hard drive can put up with this anyway :) + }; + + uint32_t mix_rate = 48000; + AudioServer::SpeakerMode speaker_mode = AudioServer::SPEAKER_MODE_STEREO; + String base_path; + uint32_t frame_count = 0; + uint32_t fps = 0; + + uint32_t audio_block_size = 0; + + Ref<FileAccess> f_wav; + uint32_t wav_data_size_pos = 0; + + String zeros_str(uint32_t p_index); + +protected: + virtual uint32_t get_audio_mix_rate() const override; + virtual AudioServer::SpeakerMode get_audio_speaker_mode() const override; + virtual void get_supported_extensions(List<String> *r_extensions) const override; + + virtual Error write_begin(const Size2i &p_movie_size, uint32_t p_fps, const String &p_base_path) override; + virtual Error write_frame(const Ref<Image> &p_image, const int32_t *p_audio_data) override; + virtual void write_end() override; + + virtual bool handles_file(const String &p_path) const override; + +public: + MovieWriterPNGWAV(); +}; + +#endif // VIDEO_WRITER_H diff --git a/servers/movie_writer/movie_writer_mjpeg.cpp b/servers/movie_writer/movie_writer_mjpeg.cpp new file mode 100644 index 0000000000..b0c65e768d --- /dev/null +++ b/servers/movie_writer/movie_writer_mjpeg.cpp @@ -0,0 +1,263 @@ +/*************************************************************************/ +/* movie_writer_mjpeg.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "movie_writer_mjpeg.h" +#include "core/config/project_settings.h" + +uint32_t MovieWriterMJPEG::get_audio_mix_rate() const { + return mix_rate; +} +AudioServer::SpeakerMode MovieWriterMJPEG::get_audio_speaker_mode() const { + return speaker_mode; +} + +bool MovieWriterMJPEG::handles_file(const String &p_path) const { + return p_path.get_extension().to_lower() == "avi"; +} + +void MovieWriterMJPEG::get_supported_extensions(List<String> *r_extensions) const { + r_extensions->push_back("avi"); +} + +Error MovieWriterMJPEG::write_begin(const Size2i &p_movie_size, uint32_t p_fps, const String &p_base_path) { + // Quick & Dirty MJPEG Code based on - https://docs.microsoft.com/en-us/windows/win32/directshow/avi-riff-file-reference + + base_path = p_base_path.get_basename(); + if (base_path.is_relative_path()) { + base_path = "res://" + base_path; + } + + base_path += ".avi"; + + f = FileAccess::open(base_path, FileAccess::WRITE_READ); + + fps = p_fps; + + ERR_FAIL_COND_V(f.is_null(), ERR_CANT_OPEN); + + f->store_buffer((const uint8_t *)"RIFF", 4); + f->store_32(0); // Total length (update later) + f->store_buffer((const uint8_t *)"AVI ", 4); + f->store_buffer((const uint8_t *)"LIST", 4); + f->store_32(300); // 4 + 4 + 4 + 56 + 4 + 4 + 132 + 4 + 4 + 84 + f->store_buffer((const uint8_t *)"hdrl", 4); + f->store_buffer((const uint8_t *)"avih", 4); + f->store_32(56); + + f->store_32(1000000 / p_fps); // Microsecs per frame. + f->store_32(7000); // Max bytes per second + f->store_32(0); // Padding Granularity + f->store_32(16); + total_frames_ofs = f->get_position(); + f->store_32(0); // Total frames (update later) + f->store_32(0); // Initial frames + f->store_32(1); // Streams + f->store_32(0); // Suggested buffer size + f->store_32(p_movie_size.width); // Movie Width + f->store_32(p_movie_size.height); // Movie Height + for (uint32_t i = 0; i < 4; i++) { + f->store_32(0); // Reserved. + } + f->store_buffer((const uint8_t *)"LIST", 4); + f->store_32(132); // 4 + 4 + 4 + 48 + 4 + 4 + 40 + 4 + 4 + 16 + f->store_buffer((const uint8_t *)"strl", 4); + f->store_buffer((const uint8_t *)"strh", 4); + f->store_32(48); + f->store_buffer((const uint8_t *)"vids", 4); + f->store_buffer((const uint8_t *)"MJPG", 4); + f->store_32(0); // Flags + f->store_16(0); // Priority + f->store_16(0); // Language + f->store_32(0); // Initial Frames + f->store_32(1); // Scale + f->store_32(p_fps); // FPS + f->store_32(0); // Start + total_frames_ofs2 = f->get_position(); + f->store_32(0); // Number of frames (to be updated later) + f->store_32(0); // Suggested Buffer Size + f->store_32(0); // Quality + f->store_32(0); // Sample Size + + f->store_buffer((const uint8_t *)"strf", 4); + f->store_32(40); // Size. + f->store_32(40); // Size. + + f->store_32(p_movie_size.width); // Width + f->store_32(p_movie_size.height); // Width + f->store_16(1); // Planes + f->store_16(24); // Bitcount + f->store_buffer((const uint8_t *)"MJPG", 4); // Compression + + f->store_32(((p_movie_size.width * 24 / 8 + 3) & 0xFFFFFFFC) * p_movie_size.height); // SizeImage + f->store_32(0); // XPelsXMeter + f->store_32(0); // YPelsXMeter + f->store_32(0); // ClrUsed + f->store_32(0); // ClrImportant + + f->store_buffer((const uint8_t *)"LIST", 4); + f->store_32(16); + + f->store_buffer((const uint8_t *)"odml", 4); + f->store_buffer((const uint8_t *)"dmlh", 4); + f->store_32(4); // sizes + + total_frames_ofs3 = f->get_position(); + f->store_32(0); // Number of frames (to be updated later) + + // Audio // + + const uint32_t bit_depth = 32; + uint32_t channels = 2; + switch (speaker_mode) { + case AudioServer::SPEAKER_MODE_STEREO: + channels = 2; + break; + case AudioServer::SPEAKER_SURROUND_31: + channels = 4; + break; + case AudioServer::SPEAKER_SURROUND_51: + channels = 6; + break; + case AudioServer::SPEAKER_SURROUND_71: + channels = 8; + break; + } + uint32_t blockalign = bit_depth / 8 * channels; + + f->store_buffer((const uint8_t *)"LIST", 4); + f->store_32(84); // 4 + 4 + 4 + 48 + 4 + 4 + 16 + f->store_buffer((const uint8_t *)"strl", 4); + f->store_buffer((const uint8_t *)"strh", 4); + f->store_32(48); + f->store_buffer((const uint8_t *)"auds", 4); + f->store_32(0); // Handler + f->store_32(0); // Flags + f->store_16(0); // Priority + f->store_16(0); // Language + f->store_32(0); // Initial Frames + f->store_32(blockalign); // Scale + f->store_32(mix_rate * blockalign); // mix rate + f->store_32(0); // Start + total_audio_frames_ofs4 = f->get_position(); + f->store_32(0); // Number of frames (to be updated later) + f->store_32(12288); // Suggested Buffer Size + f->store_32(0xFFFFFFFF); // Quality + f->store_32(blockalign); // Block Align to 32 bits + + audio_block_size = (mix_rate / fps) * blockalign; + + f->store_buffer((const uint8_t *)"strf", 4); + f->store_32(16); // Standard format, no extra fields + f->store_16(1); // Compression code, standard PCM + f->store_16(channels); + f->store_32(mix_rate); // Samples (frames) / Sec + f->store_32(mix_rate * blockalign); // Bytes / sec + f->store_16(blockalign); // Bytes / sec + f->store_16(bit_depth); // Bytes / sec + + f->store_buffer((const uint8_t *)"LIST", 4); + movi_data_ofs = f->get_position(); + f->store_32(0); // Number of frames (to be updated later) + f->store_buffer((const uint8_t *)"movi", 4); + + return OK; +} + +Error MovieWriterMJPEG::write_frame(const Ref<Image> &p_image, const int32_t *p_audio_data) { + ERR_FAIL_COND_V(!f.is_valid(), ERR_UNCONFIGURED); + + Vector<uint8_t> jpg_buffer = p_image->save_jpg_to_buffer(quality); + uint32_t s = jpg_buffer.size(); + + f->store_buffer((const uint8_t *)"00db", 4); // Stream 0, Video + f->store_32(jpg_buffer.size()); // sizes + f->store_buffer(jpg_buffer.ptr(), jpg_buffer.size()); + if (jpg_buffer.size() & 1) { + f->store_8(0); + s++; + } + jpg_frame_sizes.push_back(s); + + f->store_buffer((const uint8_t *)"01wb", 4); // Stream 1, Audio. + f->store_32(audio_block_size); + f->store_buffer((const uint8_t *)p_audio_data, audio_block_size); + + frame_count++; + + return OK; +} + +void MovieWriterMJPEG::write_end() { + if (f.is_valid()) { + // Finalize the file (frame indices) + f->store_buffer((const uint8_t *)"idx1", 4); + f->store_32(8 * 4 * frame_count); + uint32_t ofs = 4; + uint32_t all_data_size = 0; + for (uint32_t i = 0; i < frame_count; i++) { + f->store_buffer((const uint8_t *)"00db", 4); + f->store_32(16); // AVI_KEYFRAME + f->store_32(ofs); + f->store_32(jpg_frame_sizes[i]); + + ofs += jpg_frame_sizes[i] + 8; + + f->store_buffer((const uint8_t *)"01wb", 4); + f->store_32(16); // AVI_KEYFRAME + f->store_32(ofs); + f->store_32(audio_block_size); + + ofs += audio_block_size + 8; + all_data_size += jpg_frame_sizes[i] + audio_block_size; + } + + uint32_t file_size = f->get_position(); + f->seek(4); + f->store_32(file_size - 78); + f->seek(total_frames_ofs); + f->store_32(frame_count); + f->seek(total_frames_ofs2); + f->store_32(frame_count); + f->seek(total_frames_ofs3); + f->store_32(frame_count); + f->seek(total_audio_frames_ofs4); + f->store_32(frame_count * mix_rate / fps); + f->seek(movi_data_ofs); + f->store_32(all_data_size + 4 + 16 * frame_count); + + f.unref(); + } +} + +MovieWriterMJPEG::MovieWriterMJPEG() { + mix_rate = GLOBAL_GET("editor/movie_writer/mix_rate_hz"); + speaker_mode = AudioServer::SpeakerMode(int(GLOBAL_GET("editor/movie_writer/speaker_mode"))); + quality = GLOBAL_GET("editor/movie_writer/mjpeg_quality"); +} diff --git a/servers/movie_writer/movie_writer_mjpeg.h b/servers/movie_writer/movie_writer_mjpeg.h new file mode 100644 index 0000000000..822bedfedf --- /dev/null +++ b/servers/movie_writer/movie_writer_mjpeg.h @@ -0,0 +1,73 @@ +/*************************************************************************/ +/* movie_writer_mjpeg.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef MOVIE_WRITER_MJPEG_H +#define MOVIE_WRITER_MJPEG_H + +#include "servers/movie_writer/movie_writer.h" + +class MovieWriterMJPEG : public MovieWriter { + GDCLASS(MovieWriterMJPEG, MovieWriter) + + uint32_t mix_rate = 48000; + AudioServer::SpeakerMode speaker_mode = AudioServer::SPEAKER_MODE_STEREO; + String base_path; + uint32_t frame_count = 0; + uint32_t fps = 0; + float quality = 0.75; + + uint32_t audio_block_size = 0; + + Vector<uint32_t> jpg_frame_sizes; + + uint64_t total_frames_ofs = 0; + uint64_t total_frames_ofs2 = 0; + uint64_t total_frames_ofs3 = 0; + uint64_t total_audio_frames_ofs4 = 0; + uint64_t movi_data_ofs = 0; + + Ref<FileAccess> f; + +protected: + virtual uint32_t get_audio_mix_rate() const override; + virtual AudioServer::SpeakerMode get_audio_speaker_mode() const override; + virtual void get_supported_extensions(List<String> *r_extensions) const override; + + virtual Error write_begin(const Size2i &p_movie_size, uint32_t p_fps, const String &p_base_path) override; + virtual Error write_frame(const Ref<Image> &p_image, const int32_t *p_audio_data) override; + virtual void write_end() override; + + virtual bool handles_file(const String &p_path) const override; + +public: + MovieWriterMJPEG(); +}; + +#endif // MOVIE_WRITER_AVIJPEG_H diff --git a/servers/register_server_types.cpp b/servers/register_server_types.cpp index 8bbe987cb7..d7d2340119 100644 --- a/servers/register_server_types.cpp +++ b/servers/register_server_types.cpp @@ -57,6 +57,8 @@ #include "camera_server.h" #include "debugger/servers_debugger.h" #include "display_server.h" +#include "movie_writer/movie_writer.h" +#include "movie_writer/movie_writer_mjpeg.h" #include "navigation_server_2d.h" #include "navigation_server_3d.h" #include "physics_2d/godot_physics_server_2d.h" @@ -107,6 +109,9 @@ static bool has_server_feature_callback(const String &p_feature) { return false; } +static MovieWriterMJPEG *writer_mjpeg = nullptr; +static MovieWriterPNGWAV *writer_pngwav = nullptr; + void register_server_types() { shader_types = memnew(ShaderTypes); @@ -239,6 +244,8 @@ void register_server_types() { GDREGISTER_CLASS(PhysicsTestMotionParameters3D); GDREGISTER_CLASS(PhysicsTestMotionResult3D); + GDREGISTER_VIRTUAL_CLASS(MovieWriter); + ServersDebugger::initialize(); // Physics 2D @@ -254,11 +261,19 @@ void register_server_types() { PhysicsServer3DManager::register_server("GodotPhysics3D", &_createGodotPhysics3DCallback); PhysicsServer3DManager::set_default_server("GodotPhysics3D"); + + writer_mjpeg = memnew(MovieWriterMJPEG); + MovieWriter::add_writer(writer_mjpeg); + + writer_pngwav = memnew(MovieWriterPNGWAV); + MovieWriter::add_writer(writer_pngwav); } void unregister_server_types() { ServersDebugger::deinitialize(); memdelete(shader_types); + memdelete(writer_mjpeg); + memdelete(writer_pngwav); } void register_server_singletons() { diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp index 2e0c4e0f79..ab9ee2067f 100644 --- a/servers/rendering/renderer_viewport.cpp +++ b/servers/rendering/renderer_viewport.cpp @@ -1193,6 +1193,20 @@ void RendererViewport::viewport_set_sdf_oversize_and_scale(RID p_viewport, RS::V RSG::texture_storage->render_target_set_sdf_size_and_scale(viewport->render_target, p_size, p_scale); } +RID RendererViewport::viewport_find_from_screen_attachment(DisplayServer::WindowID p_id) const { + RID *rids = nullptr; + uint32_t rid_count = viewport_owner.get_rid_count(); + rids = (RID *)alloca(sizeof(RID *) * rid_count); + viewport_owner.fill_owned_buffer(rids); + for (uint32_t i = 0; i < rid_count; i++) { + Viewport *viewport = viewport_owner.get_or_null(rids[i]); + if (viewport->viewport_to_screen == p_id) { + return rids[i]; + } + } + return RID(); +} + bool RendererViewport::free(RID p_rid) { if (viewport_owner.owns(p_rid)) { Viewport *viewport = viewport_owner.get_or_null(p_rid); diff --git a/servers/rendering/renderer_viewport.h b/servers/rendering/renderer_viewport.h index 18385d1613..acab8063d1 100644 --- a/servers/rendering/renderer_viewport.h +++ b/servers/rendering/renderer_viewport.h @@ -282,6 +282,8 @@ public: void viewport_set_sdf_oversize_and_scale(RID p_viewport, RS::ViewportSDFOversize p_over_size, RS::ViewportSDFScale p_scale); + virtual RID viewport_find_from_screen_attachment(DisplayServer::WindowID p_id = DisplayServer::MAIN_WINDOW_ID) const; + void handle_timestamp(String p_timestamp, uint64_t p_cpu_time, uint64_t p_gpu_time); void set_default_clear_color(const Color &p_color); diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index 6008f2b00e..56ab5891ed 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -629,6 +629,7 @@ public: FUNC2(viewport_set_measure_render_time, RID, bool) FUNC1RC(double, viewport_get_measured_render_time_cpu, RID) FUNC1RC(double, viewport_get_measured_render_time_gpu, RID) + FUNC1RC(RID, viewport_find_from_screen_attachment, DisplayServer::WindowID) FUNC2(call_set_vsync_mode, DisplayServer::VSyncMode, DisplayServer::WindowID) diff --git a/servers/rendering_server.h b/servers/rendering_server.h index 39484e532a..ccef95f5f2 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -944,6 +944,8 @@ public: virtual double viewport_get_measured_render_time_cpu(RID p_viewport) const = 0; virtual double viewport_get_measured_render_time_gpu(RID p_viewport) const = 0; + virtual RID viewport_find_from_screen_attachment(DisplayServer::WindowID p_id = DisplayServer::MAIN_WINDOW_ID) const = 0; + /* SKY API */ enum SkyMode { |