Path: blob/master/modules/theora/editor/movie_writer_ogv.cpp
10278 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 "rgb2yuv.h"3435void MovieWriterOGV::push_audio(const int32_t *p_audio_data) {36// Read and process more audio.37float **vorbis_buffer = vorbis_analysis_buffer(&vd, audio_frames);3839// Deinterleave samples.40uint32_t count = 0;41for (uint32_t i = 0; i < audio_frames; i++) {42for (uint32_t j = 0; j < audio_ch; j++) {43vorbis_buffer[j][i] = p_audio_data[count] / 2147483647.f;44count++;45}46}4748vorbis_analysis_wrote(&vd, audio_frames);49}5051void MovieWriterOGV::pull_audio(bool p_last) {52ogg_packet op;5354while (vorbis_analysis_blockout(&vd, &vb) > 0) {55// Analysis, assume we want to use bitrate management.56vorbis_analysis(&vb, nullptr);57vorbis_bitrate_addblock(&vb);5859// Weld packets into the bitstream.60while (vorbis_bitrate_flushpacket(&vd, &op) > 0) {61ogg_stream_packetin(&vo, &op);62}63}6465if (p_last) {66vorbis_analysis_wrote(&vd, 0);67pull_audio();68}69}7071void MovieWriterOGV::push_video(const Ref<Image> &p_image) {72PackedByteArray data = p_image->get_data();73if (p_image->get_format() == Image::FORMAT_RGBA8) {74rgba2yuv420(y, u, v, data.ptrw(), p_image->get_width(), p_image->get_height());75} else {76rgb2yuv420(y, u, v, data.ptrw(), p_image->get_width(), p_image->get_height());77}78th_encode_ycbcr_in(td, ycbcr);79}8081void MovieWriterOGV::pull_video(bool p_last) {82ogg_packet op;8384int ret = 0;85do {86ret = th_encode_packetout(td, p_last, &op);87if (ret > 0) {88ogg_stream_packetin(&to, &op);89}90} while (ret > 0);91}9293uint32_t MovieWriterOGV::get_audio_mix_rate() const {94return mix_rate;95}9697AudioServer::SpeakerMode MovieWriterOGV::get_audio_speaker_mode() const {98return speaker_mode;99}100101bool MovieWriterOGV::handles_file(const String &p_path) const {102return p_path.get_extension().to_lower() == "ogv";103}104105void MovieWriterOGV::get_supported_extensions(List<String> *r_extensions) const {106r_extensions->push_back("ogv");107}108109Error MovieWriterOGV::write_begin(const Size2i &p_movie_size, uint32_t p_fps, const String &p_base_path) {110ERR_FAIL_COND_V_MSG((p_movie_size.width & 1) || (p_movie_size.height & 1), ERR_UNAVAILABLE, "Both video dimensions must be even.");111base_path = p_base_path.get_basename();112if (base_path.is_relative_path()) {113base_path = "res://" + base_path;114}115base_path += ".ogv";116117f = FileAccess::open(base_path, FileAccess::WRITE_READ);118ERR_FAIL_COND_V(f.is_null(), ERR_CANT_OPEN);119120fps = p_fps;121122audio_ch = 2;123switch (speaker_mode) {124case AudioServer::SPEAKER_MODE_STEREO:125audio_ch = 2;126break;127case AudioServer::SPEAKER_SURROUND_31:128audio_ch = 4;129break;130case AudioServer::SPEAKER_SURROUND_51:131audio_ch = 6;132break;133case AudioServer::SPEAKER_SURROUND_71:134audio_ch = 8;135break;136}137audio_frames = mix_rate / fps;138139// Set up Ogg output streams.140srand(time(nullptr));141ogg_stream_init(&to, rand()); // Video.142ogg_stream_init(&vo, rand()); // Audio.143144// Initialize Vorbis audio encoding.145vorbis_info_init(&vi);146int ret = vorbis_encode_init_vbr(&vi, audio_ch, mix_rate, audio_quality);147ERR_FAIL_COND_V_MSG(ret, ERR_UNAVAILABLE, "The Ogg Vorbis encoder couldn't set up a mode according to the requested quality or bitrate.");148149vorbis_comment_init(&vc);150vorbis_analysis_init(&vd, &vi);151vorbis_block_init(&vd, &vb);152153// Set up Theora encoder.154// Theora has a divisible-by-16 restriction for the encoded frame size155// scale the picture size up to the nearest /16 and calculate offsets.156int pic_w = p_movie_size.width;157int pic_h = p_movie_size.height;158int frame_w = (pic_w + 15) & ~0xF;159int frame_h = (pic_h + 15) & ~0xF;160// Force the offsets to be even so that chroma samples line up like we expect.161int pic_x = (frame_w - pic_w) / 2 & ~1;162int pic_y = (frame_h - pic_h) / 2 & ~1;163164y = (uint8_t *)memalloc(pic_w * pic_h);165u = (uint8_t *)memalloc(pic_w * pic_h / 4);166v = (uint8_t *)memalloc(pic_w * pic_h / 4);167168// We submit the buffer using the size of the picture region.169// libtheora will pad the picture region out to the full frame size for us,170// whether we pass in a full frame or not.171ycbcr[0].width = pic_w;172ycbcr[0].height = pic_h;173ycbcr[0].stride = pic_w;174ycbcr[0].data = y;175ycbcr[1].width = pic_w / 2;176ycbcr[1].height = pic_h / 2;177ycbcr[1].stride = pic_w / 2;178ycbcr[1].data = u;179ycbcr[2].width = pic_w / 2;180ycbcr[2].height = pic_h / 2;181ycbcr[2].stride = pic_w / 2;182ycbcr[2].data = v;183184th_info_init(&ti);185ti.frame_width = frame_w;186ti.frame_height = frame_h;187ti.pic_width = pic_w;188ti.pic_height = pic_h;189ti.pic_x = pic_x;190ti.pic_y = pic_y;191ti.fps_numerator = fps;192ti.fps_denominator = 1;193ti.aspect_numerator = 1;194ti.aspect_denominator = 1;195ti.colorspace = TH_CS_UNSPECIFIED;196// Account for the Ogg page overhead.197// This is 1 byte per 255 for lacing values, plus 26 bytes per 4096 bytes for198// the page header, plus approximately 1/2 byte per packet (not accounted for here).199ti.target_bitrate = (int)(64870 * (ogg_int64_t)video_bitrate >> 16);200ti.quality = video_quality * 63;201ti.pixel_fmt = TH_PF_420;202td = th_encode_alloc(&ti);203th_info_clear(&ti);204ERR_FAIL_NULL_V_MSG(td, ERR_UNCONFIGURED, "Couldn't create a Theora encoder instance. Check that the video parameters are valid.");205206// Setting just the granule shift only allows power-of-two keyframe spacing.207// Set the actual requested spacing.208ret = th_encode_ctl(td, TH_ENCCTL_SET_KEYFRAME_FREQUENCY_FORCE, &keyframe_frequency, sizeof(keyframe_frequency));209if (ret < 0) {210ERR_PRINT("Couldn't set keyframe interval.");211}212213// Speed should also be set after the current encoder mode is established,214// since the available speed levels may change depending on the encoder mode.215if (speed >= 0) {216int speed_max;217ret = th_encode_ctl(td, TH_ENCCTL_GET_SPLEVEL_MAX, &speed_max, sizeof(speed_max));218if (ret < 0) {219WARN_PRINT("Couldn't determine maximum speed level.");220speed_max = 0;221}222ret = th_encode_ctl(td, TH_ENCCTL_SET_SPLEVEL, &speed, sizeof(speed));223if (ret < 0) {224if (ret < 0) {225WARN_PRINT(vformat("Couldn't set speed level to %d of %d.", speed, speed_max));226}227if (speed > speed_max) {228WARN_PRINT(vformat("Setting speed level to %d instead.", speed_max));229}230ret = th_encode_ctl(td, TH_ENCCTL_SET_SPLEVEL, &speed_max, sizeof(speed_max));231if (ret < 0) {232WARN_PRINT(vformat("Couldn't set speed level to %d of %d.", speed_max, speed_max));233}234}235}236237// Write the bitstream header packets with proper page interleave.238th_comment_init(&tc);239// The first packet will get its own page automatically.240ogg_packet op;241if (th_encode_flushheader(td, &tc, &op) <= 0) {242ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Theora library error.");243}244245ogg_stream_packetin(&to, &op);246if (ogg_stream_pageout(&to, &video_page) != 1) {247ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Ogg library error.");248}249f->store_buffer(video_page.header, video_page.header_len);250f->store_buffer(video_page.body, video_page.body_len);251252// Create the remaining Theora headers.253while (true) {254ret = th_encode_flushheader(td, &tc, &op);255if (ret < 0) {256ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Theora library error.");257} else if (ret == 0) {258break;259}260ogg_stream_packetin(&to, &op);261}262263// Vorbis streams start with 3 standard header packets.264ogg_packet id;265ogg_packet comment;266ogg_packet code;267if (vorbis_analysis_headerout(&vd, &vc, &id, &comment, &code) < 0) {268ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Vorbis library error.");269}270271// ID header is automatically placed in its own page.272ogg_stream_packetin(&vo, &id);273if (ogg_stream_pageout(&vo, &audio_page) != 1) {274ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Ogg library error.");275}276f->store_buffer(audio_page.header, audio_page.header_len);277f->store_buffer(audio_page.body, audio_page.body_len);278279// Append remaining Vorbis header packets.280ogg_stream_packetin(&vo, &comment);281ogg_stream_packetin(&vo, &code);282283// Flush the rest of our headers. This ensures the actual data in each stream will start on a new page, as per spec.284while (true) {285ret = ogg_stream_flush(&to, &video_page);286if (ret < 0) {287ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Ogg library error.");288} else if (ret == 0) {289break;290}291f->store_buffer(video_page.header, video_page.header_len);292f->store_buffer(video_page.body, video_page.body_len);293}294295while (true) {296ret = ogg_stream_flush(&vo, &audio_page);297if (ret < 0) {298ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "Internal Ogg library error.");299} else if (ret == 0) {300break;301}302f->store_buffer(audio_page.header, audio_page.header_len);303f->store_buffer(audio_page.body, audio_page.body_len);304}305306return OK;307}308309// The order of the operations has been chosen so we're one frame behind writing to the stream so we can put the eos310// mark in the last frame.311// Flushing streams to the file every X frames is done to improve audio/video page interleaving thus avoiding large runs312// of video or audio pages.313Error MovieWriterOGV::write_frame(const Ref<Image> &p_image, const int32_t *p_audio_data) {314ERR_FAIL_COND_V(f.is_null() || td == nullptr, ERR_UNCONFIGURED);315316frame_count++;317318pull_audio();319pull_video();320321if ((frame_count % 8) == 0) {322write_to_file();323}324325push_audio(p_audio_data);326push_video(p_image);327328return OK;329}330331void MovieWriterOGV::save_page(ogg_page page) {332unsigned int page_size = page.header_len + page.body_len;333if (page_size > backup_page_size) {334backup_page_data = (unsigned char *)memrealloc(backup_page_data, page_size);335backup_page_size = page_size;336}337backup_page.header = backup_page_data;338backup_page.header_len = page.header_len;339backup_page.body = backup_page_data + page.header_len;340backup_page.body_len = page.body_len;341memcpy(backup_page.header, page.header, page.header_len);342memcpy(backup_page.body, page.body, page.body_len);343}344345void MovieWriterOGV::restore_page(ogg_page *page) {346page->header = backup_page.header;347page->header_len = backup_page.header_len;348page->body = backup_page.body;349page->body_len = backup_page.body_len;350}351352// The added complexity here is because we have to ensure pages are written in ascending timestamp order.353// libOgg doesn't allow checking the next page granulepos without requesting the page, and once requested it can't be354// returned, thus, we need to save it so that it doesn't get erased by the next `ogg_stream_packetin` call.355void MovieWriterOGV::write_to_file(bool p_finish) {356if (audio_flag) {357restore_page(&audio_page);358} else {359audio_flag = ogg_stream_flush(&vo, &audio_page);360}361if (video_flag) {362restore_page(&video_page);363} else {364video_flag = ogg_stream_flush(&to, &video_page);365}366367bool finishing = p_finish && (audio_flag || video_flag);368while (finishing || (audio_flag && video_flag)) {369double audiotime = vorbis_granule_time(&vd, ogg_page_granulepos(&audio_page));370double videotime = th_granule_time(td, ogg_page_granulepos(&video_page));371bool video_first = audiotime >= videotime;372373if (video_flag && video_first) {374// Flush a video page.375f->store_buffer(video_page.header, video_page.header_len);376f->store_buffer(video_page.body, video_page.body_len);377video_flag = ogg_stream_flush(&to, &video_page) > 0;378} else {379// Flush an audio page.380f->store_buffer(audio_page.header, audio_page.header_len);381f->store_buffer(audio_page.body, audio_page.body_len);382audio_flag = ogg_stream_flush(&vo, &audio_page) > 0;383}384finishing = p_finish && (audio_flag || video_flag);385}386387if (video_flag) {388save_page(video_page);389} else if (audio_flag) {390save_page(audio_page);391}392}393394void MovieWriterOGV::write_end() {395pull_audio(true);396pull_video(true);397write_to_file(true);398399th_encode_free(td);400401ogg_stream_clear(&vo);402vorbis_block_clear(&vb);403vorbis_dsp_clear(&vd);404vorbis_comment_clear(&vc);405vorbis_info_clear(&vi);406407ogg_stream_clear(&to);408th_comment_clear(&tc);409410memfree(y);411memfree(u);412memfree(v);413414if (backup_page_data != nullptr) {415memfree(backup_page_data);416}417418if (f.is_valid()) {419f.unref();420}421}422423MovieWriterOGV::MovieWriterOGV() {424mix_rate = GLOBAL_GET("editor/movie_writer/mix_rate");425speaker_mode = AudioServer::SpeakerMode(int(GLOBAL_GET("editor/movie_writer/speaker_mode")));426video_quality = GLOBAL_GET("editor/movie_writer/video_quality");427audio_quality = GLOBAL_GET("editor/movie_writer/ogv/audio_quality");428speed = GLOBAL_GET("editor/movie_writer/ogv/encoding_speed");429keyframe_frequency = GLOBAL_GET("editor/movie_writer/ogv/keyframe_interval");430}431432433