Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/theora/editor/movie_writer_ogv.cpp
11353 views
1
/**************************************************************************/
2
/* movie_writer_ogv.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_ogv.h"
32
33
#include "core/config/project_settings.h"
34
#include "core/io/file_access.h"
35
#include "rgb2yuv.h"
36
37
void MovieWriterOGV::push_audio(const int32_t *p_audio_data) {
38
// Read and process more audio.
39
float **vorbis_buffer = vorbis_analysis_buffer(&vd, audio_frames);
40
41
// Deinterleave samples.
42
uint32_t count = 0;
43
for (uint32_t i = 0; i < audio_frames; i++) {
44
for (uint32_t j = 0; j < audio_ch; j++) {
45
vorbis_buffer[j][i] = p_audio_data[count] / 2147483647.f;
46
count++;
47
}
48
}
49
50
vorbis_analysis_wrote(&vd, audio_frames);
51
}
52
53
void MovieWriterOGV::pull_audio(bool p_last) {
54
ogg_packet op;
55
56
while (vorbis_analysis_blockout(&vd, &vb) > 0) {
57
// Analysis, assume we want to use bitrate management.
58
vorbis_analysis(&vb, nullptr);
59
vorbis_bitrate_addblock(&vb);
60
61
// Weld packets into the bitstream.
62
while (vorbis_bitrate_flushpacket(&vd, &op) > 0) {
63
ogg_stream_packetin(&vo, &op);
64
}
65
}
66
67
if (p_last) {
68
vorbis_analysis_wrote(&vd, 0);
69
pull_audio();
70
}
71
}
72
73
void MovieWriterOGV::push_video(const Ref<Image> &p_image) {
74
PackedByteArray data = p_image->get_data();
75
if (p_image->get_format() == Image::FORMAT_RGBA8) {
76
rgba2yuv420(y, u, v, data.ptrw(), p_image->get_width(), p_image->get_height());
77
} else {
78
rgb2yuv420(y, u, v, data.ptrw(), p_image->get_width(), p_image->get_height());
79
}
80
th_encode_ycbcr_in(td, ycbcr);
81
}
82
83
void MovieWriterOGV::pull_video(bool p_last) {
84
ogg_packet op;
85
86
int ret = 0;
87
do {
88
ret = th_encode_packetout(td, p_last, &op);
89
if (ret > 0) {
90
ogg_stream_packetin(&to, &op);
91
}
92
} while (ret > 0);
93
}
94
95
uint32_t MovieWriterOGV::get_audio_mix_rate() const {
96
return mix_rate;
97
}
98
99
AudioServer::SpeakerMode MovieWriterOGV::get_audio_speaker_mode() const {
100
return speaker_mode;
101
}
102
103
bool MovieWriterOGV::handles_file(const String &p_path) const {
104
return p_path.get_extension().to_lower() == "ogv";
105
}
106
107
void MovieWriterOGV::get_supported_extensions(List<String> *r_extensions) const {
108
r_extensions->push_back("ogv");
109
}
110
111
Error MovieWriterOGV::write_begin(const Size2i &p_movie_size, uint32_t p_fps, const String &p_base_path) {
112
ERR_FAIL_COND_V_MSG((p_movie_size.width & 1) || (p_movie_size.height & 1), ERR_UNAVAILABLE, "Both video dimensions must be even.");
113
base_path = p_base_path.get_basename();
114
if (base_path.is_relative_path()) {
115
base_path = "res://" + base_path;
116
}
117
base_path += ".ogv";
118
119
f = FileAccess::open(base_path, FileAccess::WRITE_READ);
120
ERR_FAIL_COND_V(f.is_null(), ERR_CANT_OPEN);
121
122
fps = p_fps;
123
124
audio_ch = 2;
125
switch (speaker_mode) {
126
case AudioServer::SPEAKER_MODE_STEREO:
127
audio_ch = 2;
128
break;
129
case AudioServer::SPEAKER_SURROUND_31:
130
audio_ch = 4;
131
break;
132
case AudioServer::SPEAKER_SURROUND_51:
133
audio_ch = 6;
134
break;
135
case AudioServer::SPEAKER_SURROUND_71:
136
audio_ch = 8;
137
break;
138
}
139
audio_frames = mix_rate / fps;
140
141
// Set up Ogg output streams.
142
srand(time(nullptr));
143
ogg_stream_init(&to, rand()); // Video.
144
ogg_stream_init(&vo, rand()); // Audio.
145
146
// Initialize Vorbis audio encoding.
147
vorbis_info_init(&vi);
148
int ret = vorbis_encode_init_vbr(&vi, audio_ch, mix_rate, audio_quality);
149
ERR_FAIL_COND_V_MSG(ret, ERR_UNAVAILABLE, "The Ogg Vorbis encoder couldn't set up a mode according to the requested quality or bitrate.");
150
151
vorbis_comment_init(&vc);
152
vorbis_analysis_init(&vd, &vi);
153
vorbis_block_init(&vd, &vb);
154
155
// Set up Theora encoder.
156
// Theora has a divisible-by-16 restriction for the encoded frame size
157
// scale the picture size up to the nearest /16 and calculate offsets.
158
int pic_w = p_movie_size.width;
159
int pic_h = p_movie_size.height;
160
int frame_w = (pic_w + 15) & ~0xF;
161
int frame_h = (pic_h + 15) & ~0xF;
162
// Force the offsets to be even so that chroma samples line up like we expect.
163
int pic_x = (frame_w - pic_w) / 2 & ~1;
164
int pic_y = (frame_h - pic_h) / 2 & ~1;
165
166
y = (uint8_t *)memalloc(pic_w * pic_h);
167
u = (uint8_t *)memalloc(pic_w * pic_h / 4);
168
v = (uint8_t *)memalloc(pic_w * pic_h / 4);
169
170
// We submit the buffer using the size of the picture region.
171
// libtheora will pad the picture region out to the full frame size for us,
172
// whether we pass in a full frame or not.
173
ycbcr[0].width = pic_w;
174
ycbcr[0].height = pic_h;
175
ycbcr[0].stride = pic_w;
176
ycbcr[0].data = y;
177
ycbcr[1].width = pic_w / 2;
178
ycbcr[1].height = pic_h / 2;
179
ycbcr[1].stride = pic_w / 2;
180
ycbcr[1].data = u;
181
ycbcr[2].width = pic_w / 2;
182
ycbcr[2].height = pic_h / 2;
183
ycbcr[2].stride = pic_w / 2;
184
ycbcr[2].data = v;
185
186
th_info_init(&ti);
187
ti.frame_width = frame_w;
188
ti.frame_height = frame_h;
189
ti.pic_width = pic_w;
190
ti.pic_height = pic_h;
191
ti.pic_x = pic_x;
192
ti.pic_y = pic_y;
193
ti.fps_numerator = fps;
194
ti.fps_denominator = 1;
195
ti.aspect_numerator = 1;
196
ti.aspect_denominator = 1;
197
ti.colorspace = TH_CS_UNSPECIFIED;
198
// Account for the Ogg page overhead.
199
// This is 1 byte per 255 for lacing values, plus 26 bytes per 4096 bytes for
200
// the page header, plus approximately 1/2 byte per packet (not accounted for here).
201
ti.target_bitrate = (int)(64870 * (ogg_int64_t)video_bitrate >> 16);
202
ti.quality = video_quality * 63;
203
ti.pixel_fmt = TH_PF_420;
204
td = th_encode_alloc(&ti);
205
th_info_clear(&ti);
206
ERR_FAIL_NULL_V_MSG(td, ERR_UNCONFIGURED, "Couldn't create a Theora encoder instance. Check that the video parameters are valid.");
207
208
// Setting just the granule shift only allows power-of-two keyframe spacing.
209
// Set the actual requested spacing.
210
ret = th_encode_ctl(td, TH_ENCCTL_SET_KEYFRAME_FREQUENCY_FORCE, &keyframe_frequency, sizeof(keyframe_frequency));
211
if (ret < 0) {
212
ERR_PRINT("Couldn't set keyframe interval.");
213
}
214
215
// Speed should also be set after the current encoder mode is established,
216
// since the available speed levels may change depending on the encoder mode.
217
if (speed >= 0) {
218
int speed_max;
219
ret = th_encode_ctl(td, TH_ENCCTL_GET_SPLEVEL_MAX, &speed_max, sizeof(speed_max));
220
if (ret < 0) {
221
WARN_PRINT("Couldn't determine maximum speed level.");
222
speed_max = 0;
223
}
224
ret = th_encode_ctl(td, TH_ENCCTL_SET_SPLEVEL, &speed, sizeof(speed));
225
if (ret < 0) {
226
if (ret < 0) {
227
WARN_PRINT(vformat("Couldn't set speed level to %d of %d.", speed, speed_max));
228
}
229
if (speed > speed_max) {
230
WARN_PRINT(vformat("Setting speed level to %d instead.", speed_max));
231
}
232
ret = th_encode_ctl(td, TH_ENCCTL_SET_SPLEVEL, &speed_max, sizeof(speed_max));
233
if (ret < 0) {
234
WARN_PRINT(vformat("Couldn't set speed level to %d of %d.", speed_max, speed_max));
235
}
236
}
237
}
238
239
// Write the bitstream header packets with proper page interleave.
240
th_comment_init(&tc);
241
// The first packet will get its own page automatically.
242
ogg_packet op;
243
if (th_encode_flushheader(td, &tc, &op) <= 0) {
244
ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Theora library error.");
245
}
246
247
ogg_stream_packetin(&to, &op);
248
if (ogg_stream_pageout(&to, &video_page) != 1) {
249
ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Ogg library error.");
250
}
251
f->store_buffer(video_page.header, video_page.header_len);
252
f->store_buffer(video_page.body, video_page.body_len);
253
254
// Create the remaining Theora headers.
255
while (true) {
256
ret = th_encode_flushheader(td, &tc, &op);
257
if (ret < 0) {
258
ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Theora library error.");
259
} else if (ret == 0) {
260
break;
261
}
262
ogg_stream_packetin(&to, &op);
263
}
264
265
// Vorbis streams start with 3 standard header packets.
266
ogg_packet id;
267
ogg_packet comment;
268
ogg_packet code;
269
if (vorbis_analysis_headerout(&vd, &vc, &id, &comment, &code) < 0) {
270
ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Vorbis library error.");
271
}
272
273
// ID header is automatically placed in its own page.
274
ogg_stream_packetin(&vo, &id);
275
if (ogg_stream_pageout(&vo, &audio_page) != 1) {
276
ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Ogg library error.");
277
}
278
f->store_buffer(audio_page.header, audio_page.header_len);
279
f->store_buffer(audio_page.body, audio_page.body_len);
280
281
// Append remaining Vorbis header packets.
282
ogg_stream_packetin(&vo, &comment);
283
ogg_stream_packetin(&vo, &code);
284
285
// Flush the rest of our headers. This ensures the actual data in each stream will start on a new page, as per spec.
286
while (true) {
287
ret = ogg_stream_flush(&to, &video_page);
288
if (ret < 0) {
289
ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Ogg library error.");
290
} else if (ret == 0) {
291
break;
292
}
293
f->store_buffer(video_page.header, video_page.header_len);
294
f->store_buffer(video_page.body, video_page.body_len);
295
}
296
297
while (true) {
298
ret = ogg_stream_flush(&vo, &audio_page);
299
if (ret < 0) {
300
ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Ogg library error.");
301
} else if (ret == 0) {
302
break;
303
}
304
f->store_buffer(audio_page.header, audio_page.header_len);
305
f->store_buffer(audio_page.body, audio_page.body_len);
306
}
307
308
return OK;
309
}
310
311
// The order of the operations has been chosen so we're one frame behind writing to the stream so we can put the eos
312
// mark in the last frame.
313
// Flushing streams to the file every X frames is done to improve audio/video page interleaving thus avoiding large runs
314
// of video or audio pages.
315
Error MovieWriterOGV::write_frame(const Ref<Image> &p_image, const int32_t *p_audio_data) {
316
ERR_FAIL_COND_V(f.is_null() || td == nullptr, ERR_UNCONFIGURED);
317
318
frame_count++;
319
320
pull_audio();
321
pull_video();
322
323
if ((frame_count % 8) == 0) {
324
write_to_file();
325
}
326
327
push_audio(p_audio_data);
328
push_video(p_image);
329
330
return OK;
331
}
332
333
void MovieWriterOGV::save_page(ogg_page page) {
334
unsigned int page_size = page.header_len + page.body_len;
335
if (page_size > backup_page_size) {
336
backup_page_data = (unsigned char *)memrealloc(backup_page_data, page_size);
337
backup_page_size = page_size;
338
}
339
backup_page.header = backup_page_data;
340
backup_page.header_len = page.header_len;
341
backup_page.body = backup_page_data + page.header_len;
342
backup_page.body_len = page.body_len;
343
memcpy(backup_page.header, page.header, page.header_len);
344
memcpy(backup_page.body, page.body, page.body_len);
345
}
346
347
void MovieWriterOGV::restore_page(ogg_page *page) {
348
page->header = backup_page.header;
349
page->header_len = backup_page.header_len;
350
page->body = backup_page.body;
351
page->body_len = backup_page.body_len;
352
}
353
354
// The added complexity here is because we have to ensure pages are written in ascending timestamp order.
355
// libOgg doesn't allow checking the next page granulepos without requesting the page, and once requested it can't be
356
// returned, thus, we need to save it so that it doesn't get erased by the next `ogg_stream_packetin` call.
357
void MovieWriterOGV::write_to_file(bool p_finish) {
358
if (audio_flag) {
359
restore_page(&audio_page);
360
} else {
361
audio_flag = ogg_stream_flush(&vo, &audio_page);
362
}
363
if (video_flag) {
364
restore_page(&video_page);
365
} else {
366
video_flag = ogg_stream_flush(&to, &video_page);
367
}
368
369
bool finishing = p_finish && (audio_flag || video_flag);
370
while (finishing || (audio_flag && video_flag)) {
371
double audiotime = vorbis_granule_time(&vd, ogg_page_granulepos(&audio_page));
372
double videotime = th_granule_time(td, ogg_page_granulepos(&video_page));
373
bool video_first = audiotime >= videotime;
374
375
if (video_flag && video_first) {
376
// Flush a video page.
377
f->store_buffer(video_page.header, video_page.header_len);
378
f->store_buffer(video_page.body, video_page.body_len);
379
video_flag = ogg_stream_flush(&to, &video_page) > 0;
380
} else {
381
// Flush an audio page.
382
f->store_buffer(audio_page.header, audio_page.header_len);
383
f->store_buffer(audio_page.body, audio_page.body_len);
384
audio_flag = ogg_stream_flush(&vo, &audio_page) > 0;
385
}
386
finishing = p_finish && (audio_flag || video_flag);
387
}
388
389
if (video_flag) {
390
save_page(video_page);
391
} else if (audio_flag) {
392
save_page(audio_page);
393
}
394
}
395
396
void MovieWriterOGV::write_end() {
397
pull_audio(true);
398
pull_video(true);
399
write_to_file(true);
400
401
th_encode_free(td);
402
403
ogg_stream_clear(&vo);
404
vorbis_block_clear(&vb);
405
vorbis_dsp_clear(&vd);
406
vorbis_comment_clear(&vc);
407
vorbis_info_clear(&vi);
408
409
ogg_stream_clear(&to);
410
th_comment_clear(&tc);
411
412
memfree(y);
413
memfree(u);
414
memfree(v);
415
416
if (backup_page_data != nullptr) {
417
memfree(backup_page_data);
418
}
419
420
if (f.is_valid()) {
421
f.unref();
422
}
423
}
424
425
MovieWriterOGV::MovieWriterOGV() {
426
mix_rate = GLOBAL_GET("editor/movie_writer/mix_rate");
427
speaker_mode = AudioServer::SpeakerMode(int(GLOBAL_GET("editor/movie_writer/speaker_mode")));
428
video_quality = GLOBAL_GET("editor/movie_writer/video_quality");
429
audio_quality = GLOBAL_GET("editor/movie_writer/ogv/audio_quality");
430
speed = GLOBAL_GET("editor/movie_writer/ogv/encoding_speed");
431
keyframe_frequency = GLOBAL_GET("editor/movie_writer/ogv/keyframe_interval");
432
}
433
434