Path: blob/master/modules/theora/editor/movie_writer_ogv.cpp
11353 views
/**************************************************************************/1/* movie_writer_ogv.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_ogv.h"3132#include "core/config/project_settings.h"33#include "core/io/file_access.h"34#include "rgb2yuv.h"3536void MovieWriterOGV::push_audio(const int32_t *p_audio_data) {37// Read and process more audio.38float **vorbis_buffer = vorbis_analysis_buffer(&vd, audio_frames);3940// Deinterleave samples.41uint32_t count = 0;42for (uint32_t i = 0; i < audio_frames; i++) {43for (uint32_t j = 0; j < audio_ch; j++) {44vorbis_buffer[j][i] = p_audio_data[count] / 2147483647.f;45count++;46}47}4849vorbis_analysis_wrote(&vd, audio_frames);50}5152void MovieWriterOGV::pull_audio(bool p_last) {53ogg_packet op;5455while (vorbis_analysis_blockout(&vd, &vb) > 0) {56// Analysis, assume we want to use bitrate management.57vorbis_analysis(&vb, nullptr);58vorbis_bitrate_addblock(&vb);5960// Weld packets into the bitstream.61while (vorbis_bitrate_flushpacket(&vd, &op) > 0) {62ogg_stream_packetin(&vo, &op);63}64}6566if (p_last) {67vorbis_analysis_wrote(&vd, 0);68pull_audio();69}70}7172void MovieWriterOGV::push_video(const Ref<Image> &p_image) {73PackedByteArray data = p_image->get_data();74if (p_image->get_format() == Image::FORMAT_RGBA8) {75rgba2yuv420(y, u, v, data.ptrw(), p_image->get_width(), p_image->get_height());76} else {77rgb2yuv420(y, u, v, data.ptrw(), p_image->get_width(), p_image->get_height());78}79th_encode_ycbcr_in(td, ycbcr);80}8182void MovieWriterOGV::pull_video(bool p_last) {83ogg_packet op;8485int ret = 0;86do {87ret = th_encode_packetout(td, p_last, &op);88if (ret > 0) {89ogg_stream_packetin(&to, &op);90}91} while (ret > 0);92}9394uint32_t MovieWriterOGV::get_audio_mix_rate() const {95return mix_rate;96}9798AudioServer::SpeakerMode MovieWriterOGV::get_audio_speaker_mode() const {99return speaker_mode;100}101102bool MovieWriterOGV::handles_file(const String &p_path) const {103return p_path.get_extension().to_lower() == "ogv";104}105106void MovieWriterOGV::get_supported_extensions(List<String> *r_extensions) const {107r_extensions->push_back("ogv");108}109110Error MovieWriterOGV::write_begin(const Size2i &p_movie_size, uint32_t p_fps, const String &p_base_path) {111ERR_FAIL_COND_V_MSG((p_movie_size.width & 1) || (p_movie_size.height & 1), ERR_UNAVAILABLE, "Both video dimensions must be even.");112base_path = p_base_path.get_basename();113if (base_path.is_relative_path()) {114base_path = "res://" + base_path;115}116base_path += ".ogv";117118f = FileAccess::open(base_path, FileAccess::WRITE_READ);119ERR_FAIL_COND_V(f.is_null(), ERR_CANT_OPEN);120121fps = p_fps;122123audio_ch = 2;124switch (speaker_mode) {125case AudioServer::SPEAKER_MODE_STEREO:126audio_ch = 2;127break;128case AudioServer::SPEAKER_SURROUND_31:129audio_ch = 4;130break;131case AudioServer::SPEAKER_SURROUND_51:132audio_ch = 6;133break;134case AudioServer::SPEAKER_SURROUND_71:135audio_ch = 8;136break;137}138audio_frames = mix_rate / fps;139140// Set up Ogg output streams.141srand(time(nullptr));142ogg_stream_init(&to, rand()); // Video.143ogg_stream_init(&vo, rand()); // Audio.144145// Initialize Vorbis audio encoding.146vorbis_info_init(&vi);147int ret = vorbis_encode_init_vbr(&vi, audio_ch, mix_rate, audio_quality);148ERR_FAIL_COND_V_MSG(ret, ERR_UNAVAILABLE, "The Ogg Vorbis encoder couldn't set up a mode according to the requested quality or bitrate.");149150vorbis_comment_init(&vc);151vorbis_analysis_init(&vd, &vi);152vorbis_block_init(&vd, &vb);153154// Set up Theora encoder.155// Theora has a divisible-by-16 restriction for the encoded frame size156// scale the picture size up to the nearest /16 and calculate offsets.157int pic_w = p_movie_size.width;158int pic_h = p_movie_size.height;159int frame_w = (pic_w + 15) & ~0xF;160int frame_h = (pic_h + 15) & ~0xF;161// Force the offsets to be even so that chroma samples line up like we expect.162int pic_x = (frame_w - pic_w) / 2 & ~1;163int pic_y = (frame_h - pic_h) / 2 & ~1;164165y = (uint8_t *)memalloc(pic_w * pic_h);166u = (uint8_t *)memalloc(pic_w * pic_h / 4);167v = (uint8_t *)memalloc(pic_w * pic_h / 4);168169// We submit the buffer using the size of the picture region.170// libtheora will pad the picture region out to the full frame size for us,171// whether we pass in a full frame or not.172ycbcr[0].width = pic_w;173ycbcr[0].height = pic_h;174ycbcr[0].stride = pic_w;175ycbcr[0].data = y;176ycbcr[1].width = pic_w / 2;177ycbcr[1].height = pic_h / 2;178ycbcr[1].stride = pic_w / 2;179ycbcr[1].data = u;180ycbcr[2].width = pic_w / 2;181ycbcr[2].height = pic_h / 2;182ycbcr[2].stride = pic_w / 2;183ycbcr[2].data = v;184185th_info_init(&ti);186ti.frame_width = frame_w;187ti.frame_height = frame_h;188ti.pic_width = pic_w;189ti.pic_height = pic_h;190ti.pic_x = pic_x;191ti.pic_y = pic_y;192ti.fps_numerator = fps;193ti.fps_denominator = 1;194ti.aspect_numerator = 1;195ti.aspect_denominator = 1;196ti.colorspace = TH_CS_UNSPECIFIED;197// Account for the Ogg page overhead.198// This is 1 byte per 255 for lacing values, plus 26 bytes per 4096 bytes for199// the page header, plus approximately 1/2 byte per packet (not accounted for here).200ti.target_bitrate = (int)(64870 * (ogg_int64_t)video_bitrate >> 16);201ti.quality = video_quality * 63;202ti.pixel_fmt = TH_PF_420;203td = th_encode_alloc(&ti);204th_info_clear(&ti);205ERR_FAIL_NULL_V_MSG(td, ERR_UNCONFIGURED, "Couldn't create a Theora encoder instance. Check that the video parameters are valid.");206207// Setting just the granule shift only allows power-of-two keyframe spacing.208// Set the actual requested spacing.209ret = th_encode_ctl(td, TH_ENCCTL_SET_KEYFRAME_FREQUENCY_FORCE, &keyframe_frequency, sizeof(keyframe_frequency));210if (ret < 0) {211ERR_PRINT("Couldn't set keyframe interval.");212}213214// Speed should also be set after the current encoder mode is established,215// since the available speed levels may change depending on the encoder mode.216if (speed >= 0) {217int speed_max;218ret = th_encode_ctl(td, TH_ENCCTL_GET_SPLEVEL_MAX, &speed_max, sizeof(speed_max));219if (ret < 0) {220WARN_PRINT("Couldn't determine maximum speed level.");221speed_max = 0;222}223ret = th_encode_ctl(td, TH_ENCCTL_SET_SPLEVEL, &speed, sizeof(speed));224if (ret < 0) {225if (ret < 0) {226WARN_PRINT(vformat("Couldn't set speed level to %d of %d.", speed, speed_max));227}228if (speed > speed_max) {229WARN_PRINT(vformat("Setting speed level to %d instead.", speed_max));230}231ret = th_encode_ctl(td, TH_ENCCTL_SET_SPLEVEL, &speed_max, sizeof(speed_max));232if (ret < 0) {233WARN_PRINT(vformat("Couldn't set speed level to %d of %d.", speed_max, speed_max));234}235}236}237238// Write the bitstream header packets with proper page interleave.239th_comment_init(&tc);240// The first packet will get its own page automatically.241ogg_packet op;242if (th_encode_flushheader(td, &tc, &op) <= 0) {243ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Theora library error.");244}245246ogg_stream_packetin(&to, &op);247if (ogg_stream_pageout(&to, &video_page) != 1) {248ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Ogg library error.");249}250f->store_buffer(video_page.header, video_page.header_len);251f->store_buffer(video_page.body, video_page.body_len);252253// Create the remaining Theora headers.254while (true) {255ret = th_encode_flushheader(td, &tc, &op);256if (ret < 0) {257ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Theora library error.");258} else if (ret == 0) {259break;260}261ogg_stream_packetin(&to, &op);262}263264// Vorbis streams start with 3 standard header packets.265ogg_packet id;266ogg_packet comment;267ogg_packet code;268if (vorbis_analysis_headerout(&vd, &vc, &id, &comment, &code) < 0) {269ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Vorbis library error.");270}271272// ID header is automatically placed in its own page.273ogg_stream_packetin(&vo, &id);274if (ogg_stream_pageout(&vo, &audio_page) != 1) {275ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Ogg library error.");276}277f->store_buffer(audio_page.header, audio_page.header_len);278f->store_buffer(audio_page.body, audio_page.body_len);279280// Append remaining Vorbis header packets.281ogg_stream_packetin(&vo, &comment);282ogg_stream_packetin(&vo, &code);283284// Flush the rest of our headers. This ensures the actual data in each stream will start on a new page, as per spec.285while (true) {286ret = ogg_stream_flush(&to, &video_page);287if (ret < 0) {288ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Ogg library error.");289} else if (ret == 0) {290break;291}292f->store_buffer(video_page.header, video_page.header_len);293f->store_buffer(video_page.body, video_page.body_len);294}295296while (true) {297ret = ogg_stream_flush(&vo, &audio_page);298if (ret < 0) {299ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Ogg library error.");300} else if (ret == 0) {301break;302}303f->store_buffer(audio_page.header, audio_page.header_len);304f->store_buffer(audio_page.body, audio_page.body_len);305}306307return OK;308}309310// The order of the operations has been chosen so we're one frame behind writing to the stream so we can put the eos311// mark in the last frame.312// Flushing streams to the file every X frames is done to improve audio/video page interleaving thus avoiding large runs313// of video or audio pages.314Error MovieWriterOGV::write_frame(const Ref<Image> &p_image, const int32_t *p_audio_data) {315ERR_FAIL_COND_V(f.is_null() || td == nullptr, ERR_UNCONFIGURED);316317frame_count++;318319pull_audio();320pull_video();321322if ((frame_count % 8) == 0) {323write_to_file();324}325326push_audio(p_audio_data);327push_video(p_image);328329return OK;330}331332void MovieWriterOGV::save_page(ogg_page page) {333unsigned int page_size = page.header_len + page.body_len;334if (page_size > backup_page_size) {335backup_page_data = (unsigned char *)memrealloc(backup_page_data, page_size);336backup_page_size = page_size;337}338backup_page.header = backup_page_data;339backup_page.header_len = page.header_len;340backup_page.body = backup_page_data + page.header_len;341backup_page.body_len = page.body_len;342memcpy(backup_page.header, page.header, page.header_len);343memcpy(backup_page.body, page.body, page.body_len);344}345346void MovieWriterOGV::restore_page(ogg_page *page) {347page->header = backup_page.header;348page->header_len = backup_page.header_len;349page->body = backup_page.body;350page->body_len = backup_page.body_len;351}352353// The added complexity here is because we have to ensure pages are written in ascending timestamp order.354// libOgg doesn't allow checking the next page granulepos without requesting the page, and once requested it can't be355// returned, thus, we need to save it so that it doesn't get erased by the next `ogg_stream_packetin` call.356void MovieWriterOGV::write_to_file(bool p_finish) {357if (audio_flag) {358restore_page(&audio_page);359} else {360audio_flag = ogg_stream_flush(&vo, &audio_page);361}362if (video_flag) {363restore_page(&video_page);364} else {365video_flag = ogg_stream_flush(&to, &video_page);366}367368bool finishing = p_finish && (audio_flag || video_flag);369while (finishing || (audio_flag && video_flag)) {370double audiotime = vorbis_granule_time(&vd, ogg_page_granulepos(&audio_page));371double videotime = th_granule_time(td, ogg_page_granulepos(&video_page));372bool video_first = audiotime >= videotime;373374if (video_flag && video_first) {375// Flush a video page.376f->store_buffer(video_page.header, video_page.header_len);377f->store_buffer(video_page.body, video_page.body_len);378video_flag = ogg_stream_flush(&to, &video_page) > 0;379} else {380// Flush an audio page.381f->store_buffer(audio_page.header, audio_page.header_len);382f->store_buffer(audio_page.body, audio_page.body_len);383audio_flag = ogg_stream_flush(&vo, &audio_page) > 0;384}385finishing = p_finish && (audio_flag || video_flag);386}387388if (video_flag) {389save_page(video_page);390} else if (audio_flag) {391save_page(audio_page);392}393}394395void MovieWriterOGV::write_end() {396pull_audio(true);397pull_video(true);398write_to_file(true);399400th_encode_free(td);401402ogg_stream_clear(&vo);403vorbis_block_clear(&vb);404vorbis_dsp_clear(&vd);405vorbis_comment_clear(&vc);406vorbis_info_clear(&vi);407408ogg_stream_clear(&to);409th_comment_clear(&tc);410411memfree(y);412memfree(u);413memfree(v);414415if (backup_page_data != nullptr) {416memfree(backup_page_data);417}418419if (f.is_valid()) {420f.unref();421}422}423424MovieWriterOGV::MovieWriterOGV() {425mix_rate = GLOBAL_GET("editor/movie_writer/mix_rate");426speaker_mode = AudioServer::SpeakerMode(int(GLOBAL_GET("editor/movie_writer/speaker_mode")));427video_quality = GLOBAL_GET("editor/movie_writer/video_quality");428audio_quality = GLOBAL_GET("editor/movie_writer/ogv/audio_quality");429speed = GLOBAL_GET("editor/movie_writer/ogv/encoding_speed");430keyframe_frequency = GLOBAL_GET("editor/movie_writer/ogv/keyframe_interval");431}432433434