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 | |