diff --git a/modules/gdnative/SCsub b/modules/gdnative/SCsub index fe2d8c7ce91..235f0b97bb4 100644 --- a/modules/gdnative/SCsub +++ b/modules/gdnative/SCsub @@ -19,6 +19,7 @@ Export('env_gdnative') SConscript("net/SCsub") SConscript("arvr/SCsub") SConscript("pluginscript/SCsub") +SConscript("videodecoder/SCsub") from platform_methods import run_in_subprocess diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json index c5a1fa139ef..20320e18b17 100644 --- a/modules/gdnative/gdnative_api.json +++ b/modules/gdnative/gdnative_api.json @@ -6374,6 +6374,42 @@ ] } ] + }, + { + "name": "videodecoder", + "type": "VIDEODECODER", + "version": { + "major": 0, + "minor": 1 + }, + "next": null, + "api": [ + { + "name": "godot_videodecoder_file_read", + "return_type": "godot_int", + "arguments": [ + ["void *", "file_ptr"], + ["uint8_t *", "buf"], + ["int", "buf_size"] + ] + }, + { + "name": "godot_videodecoder_file_seek", + "return_type": "int64_t", + "arguments": [ + [ "void *", "file_ptr"], + ["int64_t", "pos"], + ["int", "whence"] + ] + }, + { + "name": "godot_videodecoder_register_decoder", + "return_type": "void", + "arguments": [ + ["const godot_videodecoder_interface_gdnative *", "p_interface"] + ] + } + ] } ] } diff --git a/modules/gdnative/gdnative_builders.py b/modules/gdnative/gdnative_builders.py index ff18a3ae69e..cd356ce5136 100644 --- a/modules/gdnative/gdnative_builders.py +++ b/modules/gdnative/gdnative_builders.py @@ -46,6 +46,7 @@ def _build_gdnative_api_struct_header(api): '#include ', '#include ', '#include ', + '#include ', '', '#define GDNATIVE_API_INIT(options) do { \\\n' + ' \\\n'.join(gdnative_api_init_macro) + ' \\\n } while (0)', '', @@ -244,6 +245,7 @@ def _build_gdnative_wrapper_code(api): '#include ', '#include ', '#include ', + '#include ', '', '#include ', '', diff --git a/modules/gdnative/include/videodecoder/godot_videodecoder.h b/modules/gdnative/include/videodecoder/godot_videodecoder.h new file mode 100644 index 00000000000..360fc0f5f5d --- /dev/null +++ b/modules/gdnative/include/videodecoder/godot_videodecoder.h @@ -0,0 +1,75 @@ +/*************************************************************************/ +/* godot_videodecoder.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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. */ +/*************************************************************************/ + +#ifndef GODOT_NATIVEVIDEODECODER_H +#define GODOT_NATIVEVIDEODECODER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define GODOTAV_API_MAJOR 0 +#define GODOTAV_API_MINOR 1 + +typedef struct +{ + godot_gdnative_api_version version; + void *next; + void *(*constructor)(godot_object *); + void (*destructor)(void *); + const char *(*get_plugin_name)(void); + const char **(*get_supported_extensions)(int *count); + godot_bool (*open_file)(void *, void *); // data struct, and a FileAccess pointer + godot_real (*get_length)(const void *); + godot_real (*get_playback_position)(const void *); + void (*seek)(void *, godot_real); + void (*set_audio_track)(void *, godot_int); + void (*update)(void *, godot_real); + godot_pool_byte_array *(*get_videoframe)(void *); + godot_int (*get_audioframe)(void *, float *, int); + godot_int (*get_channels)(const void *); + godot_int (*get_mix_rate)(const void *); + godot_vector2 (*get_texture_size)(const void *); +} godot_videodecoder_interface_gdnative; + +typedef int (*GDNativeAudioMixCallback)(void *, const float *, int); + +// FileAccess wrappers for custom FFmpeg IO +godot_int GDAPI godot_videodecoder_file_read(void *file_ptr, uint8_t *buf, int buf_size); +int64_t GDAPI godot_videodecoder_file_seek(void *file_ptr, int64_t pos, int whence); +void GDAPI godot_videodecoder_register_decoder(const godot_videodecoder_interface_gdnative *p_interface); + +#ifdef __cplusplus +} +#endif + +#endif /* GODOT_NATIVEVIDEODECODER_H */ diff --git a/modules/gdnative/register_types.cpp b/modules/gdnative/register_types.cpp index 62e87c3651b..8ccab959f4c 100644 --- a/modules/gdnative/register_types.cpp +++ b/modules/gdnative/register_types.cpp @@ -38,6 +38,7 @@ #include "nativescript/register_types.h" #include "net/register_types.h" #include "pluginscript/register_types.h" +#include "videodecoder/register_types.h" #include "core/engine.h" #include "core/io/resource_loader.h" @@ -326,6 +327,7 @@ void register_gdnative_types() { register_arvr_types(); register_nativescript_types(); register_pluginscript_types(); + register_videodecoder_types(); // run singletons @@ -378,6 +380,7 @@ void unregister_gdnative_types() { } singleton_gdnatives.clear(); + unregister_videodecoder_types(); unregister_pluginscript_types(); unregister_nativescript_types(); unregister_arvr_types(); diff --git a/modules/gdnative/videodecoder/SCsub b/modules/gdnative/videodecoder/SCsub new file mode 100644 index 00000000000..51b0418d6ba --- /dev/null +++ b/modules/gdnative/videodecoder/SCsub @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +import os +import methods + +Import('env') +Import('env_modules') + +env_vsdecoder_gdnative = env_modules.Clone() + +env_vsdecoder_gdnative.Append(CPPPATH=['#modules/gdnative/include/']) +env_vsdecoder_gdnative.add_source_files(env.modules_sources, '*.cpp') diff --git a/modules/gdnative/videodecoder/register_types.cpp b/modules/gdnative/videodecoder/register_types.cpp new file mode 100644 index 00000000000..64599365e5b --- /dev/null +++ b/modules/gdnative/videodecoder/register_types.cpp @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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 "class_db.h" +#include "resource_importer_av_gdnative.h" +#include "video_stream_gdnative.h" + +void register_videodecoder_types() { + +#ifdef TOOLS_ENABLED + Ref avgdn_import; + avgdn_import.instance(); + ResourceFormatImporter::get_singleton()->add_importer(avgdn_import); +#endif + ClassDB::register_class(); +} +void unregister_videodecoder_types() { +} diff --git a/modules/gdnative/videodecoder/register_types.h b/modules/gdnative/videodecoder/register_types.h new file mode 100644 index 00000000000..dd1943fc47f --- /dev/null +++ b/modules/gdnative/videodecoder/register_types.h @@ -0,0 +1,32 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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. */ +/*************************************************************************/ + +void register_videodecoder_types(); +void unregister_videodecoder_types(); diff --git a/modules/gdnative/videodecoder/resource_importer_av_gdnative.cpp b/modules/gdnative/videodecoder/resource_importer_av_gdnative.cpp new file mode 100644 index 00000000000..ff9f6118fbf --- /dev/null +++ b/modules/gdnative/videodecoder/resource_importer_av_gdnative.cpp @@ -0,0 +1,96 @@ +/*************************************************************************/ +/* resource_importer_av_gdnative.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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 "resource_importer_av_gdnative.h" + +#include "io/resource_saver.h" +#include "os/file_access.h" +#include "scene/resources/texture.h" + +String ResourceImporterAVGDNative::get_importer_name() const { + + return "AVGDNative"; +} + +String ResourceImporterAVGDNative::get_visible_name() const { + + return "AVGDNative"; +} +void ResourceImporterAVGDNative::get_recognized_extensions(List *p_extensions) const { + + Map::Element *el = VideoDecoderServer::get_instance()->get_extensions().front(); + while (el) { + p_extensions->push_back(el->key()); + el = el->next(); + } +} + +String ResourceImporterAVGDNative::get_save_extension() const { + + return "avgdnstr"; +} + +String ResourceImporterAVGDNative::get_resource_type() const { + + return "VideoStreamGDNative"; +} + +bool ResourceImporterAVGDNative::get_option_visibility(const String &p_option, const Map &p_options) const { + + return true; +} + +int ResourceImporterAVGDNative::get_preset_count() const { + + return 0; +} + +String ResourceImporterAVGDNative::get_preset_name(int p_idx) const { + + return String(); +} + +void ResourceImporterAVGDNative::get_import_options(List *r_options, int p_preset) const { + + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "loop"), true)); +} + +Error ResourceImporterAVGDNative::import(const String &p_source_file, const String &p_save_path, const Map &p_options, List *r_platform_variants, List *r_gen_files) { + + VideoStreamGDNative *stream = memnew(VideoStreamGDNative); + stream->set_file(p_source_file); + + Ref avgdn_stream = Ref(stream); + + return ResourceSaver::save(p_save_path + ".avgdnstr", avgdn_stream); +} + +ResourceImporterAVGDNative::ResourceImporterAVGDNative() { +} diff --git a/modules/gdnative/videodecoder/resource_importer_av_gdnative.h b/modules/gdnative/videodecoder/resource_importer_av_gdnative.h new file mode 100644 index 00000000000..0ec96dc72ca --- /dev/null +++ b/modules/gdnative/videodecoder/resource_importer_av_gdnative.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* resource_importer_av_gdnative.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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. */ +/*************************************************************************/ + +#ifndef RESOURCE_IMPORTER_AV_GDNATIVE_H +#define RESOURCE_IMPORTER_AV_GDNATIVE_H + +#include "video_stream_gdnative.h" + +#include "core/io/resource_import.h" + +class ResourceImporterAVGDNative : public ResourceImporter { + GDCLASS(ResourceImporterAVGDNative, ResourceImporter) +public: + virtual String get_importer_name() const; + virtual String get_visible_name() const; + virtual void get_recognized_extensions(List *p_extensions) const; + virtual String get_save_extension() const; + virtual String get_resource_type() const; + + virtual int get_preset_count() const; + virtual String get_preset_name(int p_idx) const; + + virtual void get_import_options(List *r_options, int p_preset = 0) const; + virtual bool get_option_visibility(const String &p_option, const Map &p_options) const; + + virtual Error import(const String &p_source_file, const String &p_save_path, const Map &p_options, List *r_platform_variants, List *r_gen_files = NULL); + + ResourceImporterAVGDNative(); +}; + +#endif diff --git a/modules/gdnative/videodecoder/video_stream_gdnative.cpp b/modules/gdnative/videodecoder/video_stream_gdnative.cpp new file mode 100644 index 00000000000..a959a4c8ae8 --- /dev/null +++ b/modules/gdnative/videodecoder/video_stream_gdnative.cpp @@ -0,0 +1,336 @@ +/*************************************************************************/ +/* video_stream_gdnative.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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 +#include + +VideoDecoderServer *VideoDecoderServer::instance = NULL; + +static VideoDecoderServer decoder_server; + +// 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(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(ptr); + + size_t len = file->get_len(); + if (file) { + switch (whence) { + case SEEK_SET: { + // Just for explicitness + size_t new_pos = static_cast(pos); + if (new_pos > len) { + return -1; + } + file->seek(new_pos); + pos = static_cast(file->get_position()); + return pos; + } break; + case SEEK_CUR: { + // Just in case it doesn't exist + if (pos < 0 && -pos > file->get_position()) { + return -1; + } + pos = pos + static_cast(file->get_position()); + file->seek(pos); + pos = static_cast(file->get_position()); + return pos; + } break; + case SEEK_END: { + // Just in case something goes wrong + if (-pos > len) { + return -1; + } + file->seek_end(pos); + pos = static_cast(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(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); + + 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) { + + update_texture(); + } +} + +void VideoStreamPlaybackGDNative::update_texture() { + PoolByteArray *pba = (PoolByteArray *)interface->get_videoframe(data_struct); + + if (pba == NULL) { + playing = false; + return; + } + + Ref 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(memnew(ImageTexture))), + time(0), + mix_udata(NULL), + mix_callback(NULL), + num_channels(-1), + mix_rate(0), + playing(false) {} + +VideoStreamPlaybackGDNative::~VideoStreamPlaybackGDNative() { + cleanup(); +} + +void VideoStreamPlaybackGDNative::cleanup() { + if (data_struct) + interface->destructor(data_struct); + 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 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 VideoStreamGDNative::instance_playback() { + Ref 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; +} diff --git a/modules/gdnative/videodecoder/video_stream_gdnative.h b/modules/gdnative/videodecoder/video_stream_gdnative.h new file mode 100644 index 00000000000..d203a5d04f7 --- /dev/null +++ b/modules/gdnative/videodecoder/video_stream_gdnative.h @@ -0,0 +1,201 @@ +/*************************************************************************/ +/* video_stream_gdnative.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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. */ +/*************************************************************************/ + +#ifndef VIDEO_STREAM_GDNATIVE_H +#define VIDEO_STREAM_GDNATIVE_H + +#include +#include +#include +#include + +struct VideoDecoderGDNative { + String plugin_name; + Vector supported_extensions; + const godot_videodecoder_interface_gdnative *interface; + + VideoDecoderGDNative() : + interface(NULL), + plugin_name("none") {} + + VideoDecoderGDNative(const godot_videodecoder_interface_gdnative *p_interface) : + interface(p_interface), + plugin_name(p_interface->get_plugin_name()) { + _get_supported_extensions(); + } + +private: + void _get_supported_extensions() { + supported_extensions.clear(); + int num_ext; + const char **supported_ext = interface->get_supported_extensions(&num_ext); + for (int i = 0; i < num_ext; i++) { + supported_extensions.push_back(supported_ext[i]); + } + } +}; + +class VideoDecoderServer { +private: + Vector decoders; + Map extensions; + + static VideoDecoderServer *instance; + +public: + static VideoDecoderServer *get_instance() { + return instance; + } + + const Map &get_extensions() { + return extensions; + } + + void register_decoder_interface(const godot_videodecoder_interface_gdnative *p_interface) { + VideoDecoderGDNative *decoder = memnew(VideoDecoderGDNative(p_interface)); + int index = decoders.size(); + for (int i = 0; i < decoder->supported_extensions.size(); i++) { + extensions[decoder->supported_extensions[i]] = index; + } + decoders.push_back(decoder); + } + + VideoDecoderGDNative *get_decoder(const String &extension) { + if (extensions.size() == 0 || !extensions.has(extension)) + return NULL; + return decoders[extensions[extension]]; + } + + VideoDecoderServer() { + instance = this; + } + + ~VideoDecoderServer() { + for (int i = 0; i < decoders.size(); i++) { + memdelete(decoders[i]); + } + decoders.clear(); + instance = NULL; + } +}; + +class VideoStreamPlaybackGDNative : public VideoStreamPlayback { + + GDCLASS(VideoStreamPlaybackGDNative, VideoStreamPlayback); + + Ref texture; + bool playing; + bool paused; + + Vector2 texture_size; + + void *mix_udata; + AudioMixCallback mix_callback; + + int num_channels; + float time; + int mix_rate; + double delay_compensation; + + const int AUX_BUFFER_SIZE = 1024; // Buffer 1024 samples. + float *pcm; + int pcm_write_idx; + int samples_decoded; + + void cleanup(); + void update_texture(); + +protected: + String file_name; + + FileAccess *file = NULL; + + const godot_videodecoder_interface_gdnative *interface = NULL; + void *data_struct = NULL; + +public: + VideoStreamPlaybackGDNative(); + ~VideoStreamPlaybackGDNative(); + + void set_interface(const godot_videodecoder_interface_gdnative *p_interface); + + bool open_file(const String &p_file); + + virtual void stop(); + virtual void play(); + + virtual bool is_playing() const; + + virtual void set_paused(bool p_paused); + virtual bool is_paused() const; + + virtual void set_loop(bool p_enable); + virtual bool has_loop() const; + + virtual float get_length() const; + + virtual float get_playback_position() const; + virtual void seek(float p_time); + + virtual void set_audio_track(int p_idx); + + //virtual int mix(int16_t* p_buffer,int p_frames)=0; + + virtual Ref get_texture(); + virtual void update(float p_delta); + + virtual void set_mix_callback(AudioMixCallback p_callback, void *p_userdata); + virtual int get_channels() const; + virtual int get_mix_rate() const; +}; + +class VideoStreamGDNative : public VideoStream { + + GDCLASS(VideoStreamGDNative, VideoStream); + RES_BASE_EXTENSION("avgdnstr"); + + String file; + int audio_track; + +protected: + static void + _bind_methods(); + +public: + void set_file(const String &p_file); + String get_file(); + + virtual void set_audio_track(int p_track); + virtual Ref instance_playback(); + + VideoStreamGDNative() {} +}; + +#endif