diff options
Diffstat (limited to 'modules/gdnative/videodecoder/video_stream_gdnative.cpp')
-rw-r--r-- | modules/gdnative/videodecoder/video_stream_gdnative.cpp | 389 |
1 files changed, 389 insertions, 0 deletions
diff --git a/modules/gdnative/videodecoder/video_stream_gdnative.cpp b/modules/gdnative/videodecoder/video_stream_gdnative.cpp new file mode 100644 index 0000000000..9bb1186269 --- /dev/null +++ b/modules/gdnative/videodecoder/video_stream_gdnative.cpp @@ -0,0 +1,389 @@ +/*************************************************************************/ +/* video_stream_gdnative.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "video_stream_gdnative.h" + +#include "core/project_settings.h" +#include "servers/audio_server.h" + +VideoDecoderServer *VideoDecoderServer::instance = NULL; + +static VideoDecoderServer decoder_server; + +const int AUX_BUFFER_SIZE = 1024; // Buffer 1024 samples. + +// NOTE: Callbacks for the GDNative libraries. +extern "C" { +godot_int GDAPI godot_videodecoder_file_read(void *ptr, uint8_t *buf, int buf_size) { + // ptr is a FileAccess + FileAccess *file = reinterpret_cast<FileAccess *>(ptr); + + // if file exists + if (file) { + long bytes_read = file->get_buffer(buf, buf_size); + // No bytes to read => EOF + if (bytes_read == 0) { + return 0; + } + return bytes_read; + } + return -1; +} + +int64_t GDAPI godot_videodecoder_file_seek(void *ptr, int64_t pos, int whence) { + // file + FileAccess *file = reinterpret_cast<FileAccess *>(ptr); + + if (file) { + size_t len = file->get_len(); + switch (whence) { + case SEEK_SET: { + // Just for explicitness + size_t new_pos = static_cast<size_t>(pos); + if (new_pos > len) { + return -1; + } + file->seek(new_pos); + pos = static_cast<int64_t>(file->get_position()); + return pos; + } break; + case SEEK_CUR: { + // Just in case it doesn't exist + if (pos < 0 && (size_t)-pos > file->get_position()) { + return -1; + } + pos = pos + static_cast<int>(file->get_position()); + file->seek(pos); + pos = static_cast<int64_t>(file->get_position()); + return pos; + } break; + case SEEK_END: { + // Just in case something goes wrong + if ((size_t)-pos > len) { + return -1; + } + file->seek_end(pos); + pos = static_cast<int64_t>(file->get_position()); + return pos; + } break; + default: { + // Only 4 possible options, hence default = AVSEEK_SIZE + // Asks to return the length of file + return static_cast<int64_t>(len); + } break; + } + } + // In case nothing works out. + return -1; +} + +void GDAPI godot_videodecoder_register_decoder(const godot_videodecoder_interface_gdnative *p_interface) { + + decoder_server.register_decoder_interface(p_interface); +} +} + +// VideoStreamPlaybackGDNative starts here. + +bool VideoStreamPlaybackGDNative::open_file(const String &p_file) { + ERR_FAIL_COND_V(interface == NULL, false); + file = FileAccess::open(p_file, FileAccess::READ); + bool file_opened = interface->open_file(data_struct, file); + + if (file_opened) { + num_channels = interface->get_channels(data_struct); + mix_rate = interface->get_mix_rate(data_struct); + + godot_vector2 vec = interface->get_texture_size(data_struct); + texture_size = *(Vector2 *)&vec; + + pcm = (float *)memalloc(num_channels * AUX_BUFFER_SIZE * sizeof(float)); + memset(pcm, 0, num_channels * AUX_BUFFER_SIZE * sizeof(float)); + pcm_write_idx = -1; + samples_decoded = 0; + + texture->create((int)texture_size.width, (int)texture_size.height, Image::FORMAT_RGBA8, Texture::FLAG_FILTER | Texture::FLAG_VIDEO_SURFACE); + } + + return file_opened; +} + +void VideoStreamPlaybackGDNative::update(float p_delta) { + if (!playing || paused) { + return; + } + if (!file) { + return; + } + time += p_delta; + ERR_FAIL_COND(interface == NULL); + interface->update(data_struct, p_delta); + + if (pcm_write_idx >= 0) { + // Previous remains + int mixed = mix_callback(mix_udata, pcm, samples_decoded); + if (mixed == samples_decoded) { + pcm_write_idx = -1; + } else { + samples_decoded -= mixed; + pcm_write_idx += mixed; + } + } + if (pcm_write_idx < 0) { + samples_decoded = interface->get_audioframe(data_struct, pcm, AUX_BUFFER_SIZE); + pcm_write_idx = mix_callback(mix_udata, pcm, samples_decoded); + if (pcm_write_idx == samples_decoded) { + pcm_write_idx = -1; + } else { + samples_decoded -= pcm_write_idx; + } + } + + while (interface->get_playback_position(data_struct) < time && playing) { + + update_texture(); + } +} + +void VideoStreamPlaybackGDNative::update_texture() { + PoolByteArray *pba = (PoolByteArray *)interface->get_videoframe(data_struct); + + if (pba == NULL) { + playing = false; + return; + } + + Ref<Image> img = memnew(Image(texture_size.width, texture_size.height, 0, Image::FORMAT_RGBA8, *pba)); + + texture->set_data(img); +} + +// ctor and dtor + +VideoStreamPlaybackGDNative::VideoStreamPlaybackGDNative() : + texture(Ref<ImageTexture>(memnew(ImageTexture))), + playing(false), + paused(false), + mix_udata(NULL), + mix_callback(NULL), + num_channels(-1), + time(0), + mix_rate(0), + delay_compensation(0), + pcm(NULL), + pcm_write_idx(0), + samples_decoded(0), + file(NULL), + interface(NULL), + data_struct(NULL) {} + +VideoStreamPlaybackGDNative::~VideoStreamPlaybackGDNative() { + cleanup(); +} + +void VideoStreamPlaybackGDNative::cleanup() { + if (data_struct) + interface->destructor(data_struct); + if (pcm) + memfree(pcm); + pcm = NULL; + time = 0; + num_channels = -1; + interface = NULL; + data_struct = NULL; +} + +void VideoStreamPlaybackGDNative::set_interface(const godot_videodecoder_interface_gdnative *p_interface) { + ERR_FAIL_COND(p_interface == NULL); + if (interface != NULL) { + cleanup(); + } + interface = p_interface; + data_struct = interface->constructor((godot_object *)this); +} + +// controls + +bool VideoStreamPlaybackGDNative::is_playing() const { + return playing; +} + +bool VideoStreamPlaybackGDNative::is_paused() const { + return paused; +} + +void VideoStreamPlaybackGDNative::play() { + + stop(); + + playing = true; + + delay_compensation = ProjectSettings::get_singleton()->get("audio/video_delay_compensation_ms"); + delay_compensation /= 1000.0; +} + +void VideoStreamPlaybackGDNative::stop() { + if (playing) { + seek(0); + } + playing = false; +} + +void VideoStreamPlaybackGDNative::seek(float p_time) { + ERR_FAIL_COND(interface == NULL); + interface->seek(data_struct, p_time); +} + +void VideoStreamPlaybackGDNative::set_paused(bool p_paused) { + paused = p_paused; +} + +Ref<Texture> VideoStreamPlaybackGDNative::get_texture() { + return texture; +} + +float VideoStreamPlaybackGDNative::get_length() const { + ERR_FAIL_COND_V(interface == NULL, 0); + return interface->get_length(data_struct); +} + +float VideoStreamPlaybackGDNative::get_playback_position() const { + + ERR_FAIL_COND_V(interface == NULL, 0); + return interface->get_playback_position(data_struct); +} + +bool VideoStreamPlaybackGDNative::has_loop() const { + // TODO: Implement looping? + return false; +} + +void VideoStreamPlaybackGDNative::set_loop(bool p_enable) { + // Do nothing +} + +void VideoStreamPlaybackGDNative::set_audio_track(int p_idx) { + ERR_FAIL_COND(interface == NULL); + interface->set_audio_track(data_struct, p_idx); +} + +void VideoStreamPlaybackGDNative::set_mix_callback(AudioMixCallback p_callback, void *p_userdata) { + + mix_udata = p_userdata; + mix_callback = p_callback; +} + +int VideoStreamPlaybackGDNative::get_channels() const { + ERR_FAIL_COND_V(interface == NULL, 0); + + return (num_channels > 0) ? num_channels : 0; +} + +int VideoStreamPlaybackGDNative::get_mix_rate() const { + ERR_FAIL_COND_V(interface == NULL, 0); + + return mix_rate; +} + +/* --- NOTE VideoStreamGDNative starts here. ----- */ + +Ref<VideoStreamPlayback> VideoStreamGDNative::instance_playback() { + Ref<VideoStreamPlaybackGDNative> pb = memnew(VideoStreamPlaybackGDNative); + VideoDecoderGDNative *decoder = decoder_server.get_decoder(file.get_extension().to_lower()); + if (decoder == NULL) + return NULL; + pb->set_interface(decoder->interface); + pb->set_audio_track(audio_track); + if (pb->open_file(file)) + return pb; + return NULL; +} + +void VideoStreamGDNative::set_file(const String &p_file) { + + file = p_file; +} + +String VideoStreamGDNative::get_file() { + + return file; +} + +void VideoStreamGDNative::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_file", "file"), &VideoStreamGDNative::set_file); + ClassDB::bind_method(D_METHOD("get_file"), &VideoStreamGDNative::get_file); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "file", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_file", "get_file"); +} + +void VideoStreamGDNative::set_audio_track(int p_track) { + + audio_track = p_track; +} + +/* --- NOTE ResourceFormatLoaderVideoStreamGDNative starts here. ----- */ + +RES ResourceFormatLoaderVideoStreamGDNative::load(const String &p_path, const String &p_original_path, Error *r_error) { + FileAccess *f = FileAccess::open(p_path, FileAccess::READ); + if (!f) { + if (r_error) { + *r_error = ERR_CANT_OPEN; + } + return RES(); + } + memdelete(f); + VideoStreamGDNative *stream = memnew(VideoStreamGDNative); + stream->set_file(p_path); + Ref<VideoStreamGDNative> ogv_stream = Ref<VideoStreamGDNative>(stream); + if (r_error) { + *r_error = OK; + } + return ogv_stream; +} + +void ResourceFormatLoaderVideoStreamGDNative::get_recognized_extensions(List<String> *p_extensions) const { + Map<String, int>::Element *el = VideoDecoderServer::get_instance()->get_extensions().front(); + while (el) { + p_extensions->push_back(el->key()); + el = el->next(); + } +} + +bool ResourceFormatLoaderVideoStreamGDNative::handles_type(const String &p_type) const { + return ClassDB::is_parent_class(p_type, "VideoStream"); +} + +String ResourceFormatLoaderVideoStreamGDNative::get_resource_type(const String &p_path) const { + String el = p_path.get_extension().to_lower(); + if (VideoDecoderServer::get_instance()->get_extensions().has(el)) + return "VideoStreamGDNative"; + return ""; +} |