| 1 | /**************************************************************************/ |
| 2 | /* movie_writer_mjpeg.cpp */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #include "movie_writer_mjpeg.h" |
| 32 | #include "core/config/project_settings.h" |
| 33 | |
| 34 | uint32_t MovieWriterMJPEG::get_audio_mix_rate() const { |
| 35 | return mix_rate; |
| 36 | } |
| 37 | AudioServer::SpeakerMode MovieWriterMJPEG::get_audio_speaker_mode() const { |
| 38 | return speaker_mode; |
| 39 | } |
| 40 | |
| 41 | bool MovieWriterMJPEG::handles_file(const String &p_path) const { |
| 42 | return p_path.get_extension().to_lower() == "avi" ; |
| 43 | } |
| 44 | |
| 45 | void MovieWriterMJPEG::get_supported_extensions(List<String> *r_extensions) const { |
| 46 | r_extensions->push_back("avi" ); |
| 47 | } |
| 48 | |
| 49 | Error MovieWriterMJPEG::write_begin(const Size2i &p_movie_size, uint32_t p_fps, const String &p_base_path) { |
| 50 | // Quick & Dirty MJPEG Code based on - https://docs.microsoft.com/en-us/windows/win32/directshow/avi-riff-file-reference |
| 51 | |
| 52 | base_path = p_base_path.get_basename(); |
| 53 | if (base_path.is_relative_path()) { |
| 54 | base_path = "res://" + base_path; |
| 55 | } |
| 56 | |
| 57 | base_path += ".avi" ; |
| 58 | |
| 59 | f = FileAccess::open(base_path, FileAccess::WRITE_READ); |
| 60 | |
| 61 | fps = p_fps; |
| 62 | |
| 63 | ERR_FAIL_COND_V(f.is_null(), ERR_CANT_OPEN); |
| 64 | |
| 65 | f->store_buffer((const uint8_t *)"RIFF" , 4); |
| 66 | f->store_32(0); // Total length (update later) |
| 67 | f->store_buffer((const uint8_t *)"AVI " , 4); |
| 68 | f->store_buffer((const uint8_t *)"LIST" , 4); |
| 69 | f->store_32(300); // 4 + 4 + 4 + 56 + 4 + 4 + 132 + 4 + 4 + 84 |
| 70 | f->store_buffer((const uint8_t *)"hdrl" , 4); |
| 71 | f->store_buffer((const uint8_t *)"avih" , 4); |
| 72 | f->store_32(56); |
| 73 | |
| 74 | f->store_32(1000000 / p_fps); // Microsecs per frame. |
| 75 | f->store_32(7000); // Max bytes per second |
| 76 | f->store_32(0); // Padding Granularity |
| 77 | f->store_32(16); |
| 78 | total_frames_ofs = f->get_position(); |
| 79 | f->store_32(0); // Total frames (update later) |
| 80 | f->store_32(0); // Initial frames |
| 81 | f->store_32(1); // Streams |
| 82 | f->store_32(0); // Suggested buffer size |
| 83 | f->store_32(p_movie_size.width); // Movie Width |
| 84 | f->store_32(p_movie_size.height); // Movie Height |
| 85 | for (uint32_t i = 0; i < 4; i++) { |
| 86 | f->store_32(0); // Reserved. |
| 87 | } |
| 88 | f->store_buffer((const uint8_t *)"LIST" , 4); |
| 89 | f->store_32(132); // 4 + 4 + 4 + 48 + 4 + 4 + 40 + 4 + 4 + 16 |
| 90 | f->store_buffer((const uint8_t *)"strl" , 4); |
| 91 | f->store_buffer((const uint8_t *)"strh" , 4); |
| 92 | f->store_32(48); |
| 93 | f->store_buffer((const uint8_t *)"vids" , 4); |
| 94 | f->store_buffer((const uint8_t *)"MJPG" , 4); |
| 95 | f->store_32(0); // Flags |
| 96 | f->store_16(0); // Priority |
| 97 | f->store_16(0); // Language |
| 98 | f->store_32(0); // Initial Frames |
| 99 | f->store_32(1); // Scale |
| 100 | f->store_32(p_fps); // FPS |
| 101 | f->store_32(0); // Start |
| 102 | total_frames_ofs2 = f->get_position(); |
| 103 | f->store_32(0); // Number of frames (to be updated later) |
| 104 | f->store_32(0); // Suggested Buffer Size |
| 105 | f->store_32(0); // Quality |
| 106 | f->store_32(0); // Sample Size |
| 107 | |
| 108 | f->store_buffer((const uint8_t *)"strf" , 4); |
| 109 | f->store_32(40); // Size. |
| 110 | f->store_32(40); // Size. |
| 111 | |
| 112 | f->store_32(p_movie_size.width); // Width |
| 113 | f->store_32(p_movie_size.height); // Width |
| 114 | f->store_16(1); // Planes |
| 115 | f->store_16(24); // Bitcount |
| 116 | f->store_buffer((const uint8_t *)"MJPG" , 4); // Compression |
| 117 | |
| 118 | f->store_32(((p_movie_size.width * 24 / 8 + 3) & 0xFFFFFFFC) * p_movie_size.height); // SizeImage |
| 119 | f->store_32(0); // XPelsXMeter |
| 120 | f->store_32(0); // YPelsXMeter |
| 121 | f->store_32(0); // ClrUsed |
| 122 | f->store_32(0); // ClrImportant |
| 123 | |
| 124 | f->store_buffer((const uint8_t *)"LIST" , 4); |
| 125 | f->store_32(16); |
| 126 | |
| 127 | f->store_buffer((const uint8_t *)"odml" , 4); |
| 128 | f->store_buffer((const uint8_t *)"dmlh" , 4); |
| 129 | f->store_32(4); // sizes |
| 130 | |
| 131 | total_frames_ofs3 = f->get_position(); |
| 132 | f->store_32(0); // Number of frames (to be updated later) |
| 133 | |
| 134 | // Audio // |
| 135 | |
| 136 | const uint32_t bit_depth = 32; |
| 137 | uint32_t channels = 2; |
| 138 | switch (speaker_mode) { |
| 139 | case AudioServer::SPEAKER_MODE_STEREO: |
| 140 | channels = 2; |
| 141 | break; |
| 142 | case AudioServer::SPEAKER_SURROUND_31: |
| 143 | channels = 4; |
| 144 | break; |
| 145 | case AudioServer::SPEAKER_SURROUND_51: |
| 146 | channels = 6; |
| 147 | break; |
| 148 | case AudioServer::SPEAKER_SURROUND_71: |
| 149 | channels = 8; |
| 150 | break; |
| 151 | } |
| 152 | uint32_t blockalign = bit_depth / 8 * channels; |
| 153 | |
| 154 | f->store_buffer((const uint8_t *)"LIST" , 4); |
| 155 | f->store_32(84); // 4 + 4 + 4 + 48 + 4 + 4 + 16 |
| 156 | f->store_buffer((const uint8_t *)"strl" , 4); |
| 157 | f->store_buffer((const uint8_t *)"strh" , 4); |
| 158 | f->store_32(48); |
| 159 | f->store_buffer((const uint8_t *)"auds" , 4); |
| 160 | f->store_32(0); // Handler |
| 161 | f->store_32(0); // Flags |
| 162 | f->store_16(0); // Priority |
| 163 | f->store_16(0); // Language |
| 164 | f->store_32(0); // Initial Frames |
| 165 | f->store_32(blockalign); // Scale |
| 166 | f->store_32(mix_rate * blockalign); // mix rate |
| 167 | f->store_32(0); // Start |
| 168 | total_audio_frames_ofs4 = f->get_position(); |
| 169 | f->store_32(0); // Number of frames (to be updated later) |
| 170 | f->store_32(12288); // Suggested Buffer Size |
| 171 | f->store_32(0xFFFFFFFF); // Quality |
| 172 | f->store_32(blockalign); // Block Align to 32 bits |
| 173 | |
| 174 | audio_block_size = (mix_rate / fps) * blockalign; |
| 175 | |
| 176 | f->store_buffer((const uint8_t *)"strf" , 4); |
| 177 | f->store_32(16); // Standard format, no extra fields |
| 178 | f->store_16(1); // Compression code, standard PCM |
| 179 | f->store_16(channels); |
| 180 | f->store_32(mix_rate); // Samples (frames) / Sec |
| 181 | f->store_32(mix_rate * blockalign); // Bytes / sec |
| 182 | f->store_16(blockalign); // Bytes / sec |
| 183 | f->store_16(bit_depth); // Bytes / sec |
| 184 | |
| 185 | f->store_buffer((const uint8_t *)"LIST" , 4); |
| 186 | movi_data_ofs = f->get_position(); |
| 187 | f->store_32(0); // Number of frames (to be updated later) |
| 188 | f->store_buffer((const uint8_t *)"movi" , 4); |
| 189 | |
| 190 | return OK; |
| 191 | } |
| 192 | |
| 193 | Error MovieWriterMJPEG::write_frame(const Ref<Image> &p_image, const int32_t *p_audio_data) { |
| 194 | ERR_FAIL_COND_V(!f.is_valid(), ERR_UNCONFIGURED); |
| 195 | |
| 196 | Vector<uint8_t> jpg_buffer = p_image->save_jpg_to_buffer(quality); |
| 197 | uint32_t s = jpg_buffer.size(); |
| 198 | |
| 199 | f->store_buffer((const uint8_t *)"00db" , 4); // Stream 0, Video |
| 200 | f->store_32(jpg_buffer.size()); // sizes |
| 201 | f->store_buffer(jpg_buffer.ptr(), jpg_buffer.size()); |
| 202 | if (jpg_buffer.size() & 1) { |
| 203 | f->store_8(0); |
| 204 | s++; |
| 205 | } |
| 206 | jpg_frame_sizes.push_back(s); |
| 207 | |
| 208 | f->store_buffer((const uint8_t *)"01wb" , 4); // Stream 1, Audio. |
| 209 | f->store_32(audio_block_size); |
| 210 | f->store_buffer((const uint8_t *)p_audio_data, audio_block_size); |
| 211 | |
| 212 | frame_count++; |
| 213 | |
| 214 | return OK; |
| 215 | } |
| 216 | |
| 217 | void MovieWriterMJPEG::write_end() { |
| 218 | if (f.is_valid()) { |
| 219 | // Finalize the file (frame indices) |
| 220 | f->store_buffer((const uint8_t *)"idx1" , 4); |
| 221 | f->store_32(8 * 4 * frame_count); |
| 222 | uint32_t ofs = 4; |
| 223 | uint32_t all_data_size = 0; |
| 224 | for (uint32_t i = 0; i < frame_count; i++) { |
| 225 | f->store_buffer((const uint8_t *)"00db" , 4); |
| 226 | f->store_32(16); // AVI_KEYFRAME |
| 227 | f->store_32(ofs); |
| 228 | f->store_32(jpg_frame_sizes[i]); |
| 229 | |
| 230 | ofs += jpg_frame_sizes[i] + 8; |
| 231 | |
| 232 | f->store_buffer((const uint8_t *)"01wb" , 4); |
| 233 | f->store_32(16); // AVI_KEYFRAME |
| 234 | f->store_32(ofs); |
| 235 | f->store_32(audio_block_size); |
| 236 | |
| 237 | ofs += audio_block_size + 8; |
| 238 | all_data_size += jpg_frame_sizes[i] + audio_block_size; |
| 239 | } |
| 240 | |
| 241 | uint32_t file_size = f->get_position(); |
| 242 | f->seek(4); |
| 243 | f->store_32(file_size - 78); |
| 244 | f->seek(total_frames_ofs); |
| 245 | f->store_32(frame_count); |
| 246 | f->seek(total_frames_ofs2); |
| 247 | f->store_32(frame_count); |
| 248 | f->seek(total_frames_ofs3); |
| 249 | f->store_32(frame_count); |
| 250 | f->seek(total_audio_frames_ofs4); |
| 251 | f->store_32(frame_count * mix_rate / fps); |
| 252 | f->seek(movi_data_ofs); |
| 253 | f->store_32(all_data_size + 4 + 16 * frame_count); |
| 254 | |
| 255 | f.unref(); |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | MovieWriterMJPEG::MovieWriterMJPEG() { |
| 260 | mix_rate = GLOBAL_GET("editor/movie_writer/mix_rate" ); |
| 261 | speaker_mode = AudioServer::SpeakerMode(int(GLOBAL_GET("editor/movie_writer/speaker_mode" ))); |
| 262 | quality = GLOBAL_GET("editor/movie_writer/mjpeg_quality" ); |
| 263 | } |
| 264 | |