Merge pull request #12014 from hi-ogawa/fix-video-playback
Fix video playback
This commit is contained in:
commit
03a080547d
|
@ -1,7 +1,6 @@
|
|||
|
||||
def can_build(platform):
|
||||
# return True
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def configure(env):
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
Import('env')
|
||||
Import('env_modules')
|
||||
|
||||
|
||||
stub = True
|
||||
|
||||
env_opus = env_modules.Clone()
|
||||
|
||||
# Thirdparty source files
|
||||
|
@ -212,5 +215,9 @@ if env['builtin_opus']:
|
|||
if env['builtin_libogg']:
|
||||
env_opus.Append(CPPPATH=["#thirdparty/libogg"])
|
||||
|
||||
# Module files
|
||||
env_opus.add_source_files(env.modules_sources, "*.cpp")
|
||||
if not stub:
|
||||
# Module files
|
||||
env_opus.add_source_files(env.modules_sources, "*.cpp")
|
||||
else:
|
||||
# Module files
|
||||
env_opus.add_source_files(env.modules_sources, "stub/register_types.cpp")
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
def can_build(platform):
|
||||
# return True
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def configure(env):
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*************************************************************************/
|
||||
/* register_types.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* http://www.godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2017 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 "register_types.h"
|
||||
|
||||
// Dummy module as libvorbis is needed by other modules (theora ...)
|
||||
|
||||
void register_opus_types() {}
|
||||
|
||||
void unregister_opus_types() {}
|
|
@ -0,0 +1,31 @@
|
|||
/*************************************************************************/
|
||||
/* register_types.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* http://www.godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2017 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_opus_types();
|
||||
void unregister_opus_types();
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
def can_build(platform):
|
||||
# return True
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def configure(env):
|
||||
|
|
|
@ -28,19 +28,18 @@
|
|||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
#include "register_types.h"
|
||||
|
||||
#include "resource_importer_theora.h"
|
||||
#include "video_stream_theora.h"
|
||||
|
||||
static ResourceFormatLoaderVideoStreamTheora *theora_stream_loader = NULL;
|
||||
|
||||
void register_theora_types() {
|
||||
|
||||
theora_stream_loader = memnew(ResourceFormatLoaderVideoStreamTheora);
|
||||
ResourceLoader::add_resource_format_loader(theora_stream_loader);
|
||||
#ifdef TOOLS_ENABLED
|
||||
Ref<ResourceImporterTheora> theora_import;
|
||||
theora_import.instance();
|
||||
ResourceFormatImporter::get_singleton()->add_importer(theora_import);
|
||||
#endif
|
||||
ClassDB::register_class<VideoStreamTheora>();
|
||||
}
|
||||
|
||||
void unregister_theora_types() {
|
||||
|
||||
memdelete(theora_stream_loader);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*************************************************************************/
|
||||
/* resource_importer_theora.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* http://www.godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2017 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_theora.h"
|
||||
|
||||
#include "io/resource_saver.h"
|
||||
#include "os/file_access.h"
|
||||
#include "scene/resources/texture.h"
|
||||
|
||||
String ResourceImporterTheora::get_importer_name() const {
|
||||
|
||||
return "Theora";
|
||||
}
|
||||
|
||||
String ResourceImporterTheora::get_visible_name() const {
|
||||
|
||||
return "Theora";
|
||||
}
|
||||
void ResourceImporterTheora::get_recognized_extensions(List<String> *p_extensions) const {
|
||||
|
||||
p_extensions->push_back("ogv");
|
||||
p_extensions->push_back("ogm");
|
||||
}
|
||||
|
||||
String ResourceImporterTheora::get_save_extension() const {
|
||||
return "ogvstr";
|
||||
}
|
||||
|
||||
String ResourceImporterTheora::get_resource_type() const {
|
||||
|
||||
return "VideoStreamTheora";
|
||||
}
|
||||
|
||||
bool ResourceImporterTheora::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int ResourceImporterTheora::get_preset_count() const {
|
||||
return 0;
|
||||
}
|
||||
String ResourceImporterTheora::get_preset_name(int p_idx) const {
|
||||
|
||||
return String();
|
||||
}
|
||||
|
||||
void ResourceImporterTheora::get_import_options(List<ImportOption> *r_options, int p_preset) const {
|
||||
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "loop"), true));
|
||||
}
|
||||
|
||||
Error ResourceImporterTheora::import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files) {
|
||||
|
||||
VideoStreamTheora *stream = memnew(VideoStreamTheora);
|
||||
stream->set_file(p_source_file);
|
||||
|
||||
Ref<VideoStreamTheora> ogv_stream = Ref<VideoStreamTheora>(stream);
|
||||
|
||||
return ResourceSaver::save(p_save_path + ".ogvstr", ogv_stream);
|
||||
}
|
||||
|
||||
ResourceImporterTheora::ResourceImporterTheora() {
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*************************************************************************/
|
||||
/* resource_importer_theora.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* http://www.godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2017 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 RESOURCEIMPORTEROGGTHEORA_H
|
||||
#define RESOURCEIMPORTEROGGTHEORA_H
|
||||
|
||||
#include "video_stream_theora.h"
|
||||
|
||||
#include "core/io/resource_import.h"
|
||||
|
||||
class ResourceImporterTheora : public ResourceImporter {
|
||||
GDCLASS(ResourceImporterTheora, ResourceImporter)
|
||||
public:
|
||||
virtual String get_importer_name() const;
|
||||
virtual String get_visible_name() const;
|
||||
virtual void get_recognized_extensions(List<String> *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<ImportOption> *r_options, int p_preset = 0) const;
|
||||
virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const;
|
||||
|
||||
virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = NULL);
|
||||
|
||||
ResourceImporterTheora();
|
||||
};
|
||||
|
||||
#endif // RESOURCEIMPORTEROGGTHEORA_H
|
|
@ -406,20 +406,19 @@ void VideoStreamPlaybackTheora::update(float p_delta) {
|
|||
|
||||
ogg_packet op;
|
||||
bool no_theora = false;
|
||||
bool buffer_full = false;
|
||||
|
||||
while (vorbis_p) {
|
||||
while (vorbis_p && !audio_done && !buffer_full) {
|
||||
int ret;
|
||||
float **pcm;
|
||||
|
||||
bool buffer_full = false;
|
||||
|
||||
/* if there's pending, decoded audio, grab it */
|
||||
ret = vorbis_synthesis_pcmout(&vd, &pcm);
|
||||
if (ret > 0) {
|
||||
|
||||
const int AUXBUF_LEN = 4096;
|
||||
int to_read = ret;
|
||||
int16_t aux_buffer[AUXBUF_LEN];
|
||||
float aux_buffer[AUXBUF_LEN];
|
||||
|
||||
while (to_read) {
|
||||
|
||||
|
@ -429,11 +428,7 @@ void VideoStreamPlaybackTheora::update(float p_delta) {
|
|||
|
||||
for (int j = 0; j < m; j++) {
|
||||
for (int i = 0; i < vi.channels; i++) {
|
||||
|
||||
int val = Math::fast_ftoi(pcm[i][j] * 32767.f);
|
||||
if (val > 32767) val = 32767;
|
||||
if (val < -32768) val = -32768;
|
||||
aux_buffer[count++] = val;
|
||||
aux_buffer[count++] = pcm[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -602,10 +597,9 @@ bool VideoStreamPlaybackTheora::is_playing() const {
|
|||
void VideoStreamPlaybackTheora::set_paused(bool p_paused) {
|
||||
|
||||
paused = p_paused;
|
||||
//pau = !p_paused;
|
||||
};
|
||||
|
||||
bool VideoStreamPlaybackTheora::is_paused(bool p_paused) const {
|
||||
bool VideoStreamPlaybackTheora::is_paused() const {
|
||||
|
||||
return paused;
|
||||
};
|
||||
|
@ -733,32 +727,10 @@ VideoStreamPlaybackTheora::~VideoStreamPlaybackTheora() {
|
|||
memdelete(file);
|
||||
};
|
||||
|
||||
RES ResourceFormatLoaderVideoStreamTheora::load(const String &p_path, const String &p_original_path, Error *r_error) {
|
||||
if (r_error)
|
||||
*r_error = ERR_FILE_CANT_OPEN;
|
||||
void VideoStreamTheora::_bind_methods() {
|
||||
|
||||
VideoStreamTheora *stream = memnew(VideoStreamTheora);
|
||||
stream->set_file(p_path);
|
||||
ClassDB::bind_method(D_METHOD("set_file", "file"), &VideoStreamTheora::set_file);
|
||||
ClassDB::bind_method(D_METHOD("get_file"), &VideoStreamTheora::get_file);
|
||||
|
||||
if (r_error)
|
||||
*r_error = OK;
|
||||
|
||||
return Ref<VideoStreamTheora>(stream);
|
||||
}
|
||||
|
||||
void ResourceFormatLoaderVideoStreamTheora::get_recognized_extensions(List<String> *p_extensions) const {
|
||||
|
||||
p_extensions->push_back("ogm");
|
||||
p_extensions->push_back("ogv");
|
||||
}
|
||||
bool ResourceFormatLoaderVideoStreamTheora::handles_type(const String &p_type) const {
|
||||
return (p_type == "VideoStream" || p_type == "VideoStreamTheora");
|
||||
}
|
||||
|
||||
String ResourceFormatLoaderVideoStreamTheora::get_resource_type(const String &p_path) const {
|
||||
|
||||
String exl = p_path.get_extension().to_lower();
|
||||
if (exl == "ogm" || exl == "ogv")
|
||||
return "VideoStreamTheora";
|
||||
return "";
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "file", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_file", "get_file");
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include "os/thread.h"
|
||||
#include "ring_buffer.h"
|
||||
#include "scene/resources/video_stream.h"
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
#include <theora/theoradec.h>
|
||||
#include <vorbis/codec.h>
|
||||
|
@ -129,7 +130,7 @@ public:
|
|||
virtual bool is_playing() const;
|
||||
|
||||
virtual void set_paused(bool p_paused);
|
||||
virtual bool is_paused(bool p_paused) const;
|
||||
virtual bool is_paused() const;
|
||||
|
||||
virtual void set_loop(bool p_enable);
|
||||
virtual bool has_loop() const;
|
||||
|
@ -161,10 +162,14 @@ public:
|
|||
class VideoStreamTheora : public VideoStream {
|
||||
|
||||
GDCLASS(VideoStreamTheora, VideoStream);
|
||||
RES_BASE_EXTENSION("ogvstr");
|
||||
|
||||
String file;
|
||||
int audio_track;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Ref<VideoStreamPlayback> instance_playback() {
|
||||
Ref<VideoStreamPlaybackTheora> pb = memnew(VideoStreamPlaybackTheora);
|
||||
|
@ -174,17 +179,10 @@ public:
|
|||
}
|
||||
|
||||
void set_file(const String &p_file) { file = p_file; }
|
||||
String get_file() { return file; }
|
||||
void set_audio_track(int p_track) { audio_track = p_track; }
|
||||
|
||||
VideoStreamTheora() { audio_track = 0; }
|
||||
};
|
||||
|
||||
class ResourceFormatLoaderVideoStreamTheora : public ResourceFormatLoader {
|
||||
public:
|
||||
virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = NULL);
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const;
|
||||
virtual bool handles_type(const String &p_type) const;
|
||||
virtual String get_resource_type(const String &p_path) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -5,6 +5,8 @@ Import('env_modules')
|
|||
|
||||
env_vorbis = env_modules.Clone()
|
||||
|
||||
stub = True
|
||||
|
||||
# Thirdparty source files
|
||||
if env['builtin_libvorbis']:
|
||||
thirdparty_dir = "#thirdparty/libvorbis/"
|
||||
|
@ -45,5 +47,9 @@ if env['builtin_libvorbis']:
|
|||
if env['builtin_libogg']:
|
||||
env_vorbis.Append(CPPPATH=["#thirdparty/libogg"])
|
||||
|
||||
# Godot source files
|
||||
env_vorbis.add_source_files(env.modules_sources, "*.cpp")
|
||||
if not stub:
|
||||
# Module files
|
||||
env_vorbis.add_source_files(env.modules_sources, "*.cpp")
|
||||
else:
|
||||
# Module files
|
||||
env_vorbis.add_source_files(env.modules_sources, "stub/register_types.cpp")
|
||||
|
|
|
@ -106,8 +106,6 @@ int AudioStreamPlaybackOGGVorbis::mix(int16_t *p_buffer, int p_frames) {
|
|||
break;
|
||||
}
|
||||
|
||||
//printf("to mix %i - mix me %i bytes\n",to_mix,to_mix*stream_channels*sizeof(int16_t));
|
||||
|
||||
#ifdef BIG_ENDIAN_ENABLED
|
||||
long ret = ov_read(&vf, (char *)p_buffer, todo * stream_channels * sizeof(int16_t), 1, 2, 1, ¤t_section);
|
||||
#else
|
||||
|
@ -359,7 +357,7 @@ void AudioStreamPlaybackOGGVorbis::set_paused(bool p_paused) {
|
|||
paused = p_paused;
|
||||
}
|
||||
|
||||
bool AudioStreamPlaybackOGGVorbis::is_paused(bool p_paused) const {
|
||||
bool AudioStreamPlaybackOGGVorbis::is_paused() const {
|
||||
|
||||
return paused;
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ public:
|
|||
virtual void set_loop_restart_time(float p_time) { loop_restart_time = p_time; }
|
||||
|
||||
virtual void set_paused(bool p_paused);
|
||||
virtual bool is_paused(bool p_paused) const;
|
||||
virtual bool is_paused() const;
|
||||
|
||||
virtual void set_loop(bool p_enable);
|
||||
virtual bool has_loop() const;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
def can_build(platform):
|
||||
# return True
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def configure(env):
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*************************************************************************/
|
||||
/* register_types.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* http://www.godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2017 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 "register_types.h"
|
||||
|
||||
// Dummy module as libvorbis is needed by other modules (theora ...)
|
||||
|
||||
void register_vorbis_types() {}
|
||||
|
||||
void unregister_vorbis_types() {}
|
|
@ -0,0 +1,31 @@
|
|||
/*************************************************************************/
|
||||
/* register_types.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* http://www.godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2017 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_vorbis_types();
|
||||
void unregister_vorbis_types();
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
def can_build(platform):
|
||||
# return True
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def configure(env):
|
||||
|
|
|
@ -298,7 +298,7 @@ if webm_cpu_x86:
|
|||
|
||||
if not yasm_found:
|
||||
webm_cpu_x86 = False
|
||||
print "YASM is necessary for WebM SIMD optimizations."
|
||||
print("YASM is necessary for WebM SIMD optimizations.")
|
||||
|
||||
webm_simd_optimizations = False
|
||||
|
||||
|
@ -345,7 +345,7 @@ if webm_cpu_arm:
|
|||
webm_simd_optimizations = True
|
||||
|
||||
if webm_simd_optimizations == False:
|
||||
print "WebM SIMD optimizations are disabled. Check if your CPU architecture, CPU bits or platform are supported!"
|
||||
print("WebM SIMD optimizations are disabled. Check if your CPU architecture, CPU bits or platform are supported!")
|
||||
|
||||
|
||||
env_libvpx.add_source_files(env.modules_sources, libvpx_sources)
|
||||
|
|
|
@ -28,19 +28,18 @@
|
|||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
#include "register_types.h"
|
||||
|
||||
#include "resource_importer_webm.h"
|
||||
#include "video_stream_webm.h"
|
||||
|
||||
static ResourceFormatLoaderVideoStreamWebm *webm_stream_loader = NULL;
|
||||
|
||||
void register_webm_types() {
|
||||
|
||||
webm_stream_loader = memnew(ResourceFormatLoaderVideoStreamWebm);
|
||||
ResourceLoader::add_resource_format_loader(webm_stream_loader);
|
||||
#ifdef TOOLS_ENABLED
|
||||
Ref<ResourceImporterWebm> webm_import;
|
||||
webm_import.instance();
|
||||
ResourceFormatImporter::get_singleton()->add_importer(webm_import);
|
||||
#endif
|
||||
ClassDB::register_class<VideoStreamWebm>();
|
||||
}
|
||||
|
||||
void unregister_webm_types() {
|
||||
|
||||
memdelete(webm_stream_loader);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
/*************************************************************************/
|
||||
/* resource_importer_webm.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* http://www.godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2017 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_webm.h"
|
||||
|
||||
#include "io/resource_saver.h"
|
||||
#include "os/file_access.h"
|
||||
#include "scene/resources/texture.h"
|
||||
#include "video_stream_webm.h"
|
||||
|
||||
String ResourceImporterWebm::get_importer_name() const {
|
||||
|
||||
return "Webm";
|
||||
}
|
||||
|
||||
String ResourceImporterWebm::get_visible_name() const {
|
||||
|
||||
return "Webm";
|
||||
}
|
||||
void ResourceImporterWebm::get_recognized_extensions(List<String> *p_extensions) const {
|
||||
|
||||
p_extensions->push_back("webm");
|
||||
}
|
||||
|
||||
String ResourceImporterWebm::get_save_extension() const {
|
||||
return "webmstr";
|
||||
}
|
||||
|
||||
String ResourceImporterWebm::get_resource_type() const {
|
||||
|
||||
return "VideoStreamWebm";
|
||||
}
|
||||
|
||||
bool ResourceImporterWebm::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int ResourceImporterWebm::get_preset_count() const {
|
||||
return 0;
|
||||
}
|
||||
String ResourceImporterWebm::get_preset_name(int p_idx) const {
|
||||
|
||||
return String();
|
||||
}
|
||||
|
||||
void ResourceImporterWebm::get_import_options(List<ImportOption> *r_options, int p_preset) const {
|
||||
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "loop"), true));
|
||||
}
|
||||
|
||||
Error ResourceImporterWebm::import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files) {
|
||||
|
||||
FileAccess *f = FileAccess::open(p_source_file, FileAccess::READ);
|
||||
if (!f) {
|
||||
ERR_FAIL_COND_V(!f, ERR_CANT_OPEN);
|
||||
}
|
||||
memdelete(f);
|
||||
|
||||
VideoStreamWebm *stream = memnew(VideoStreamWebm);
|
||||
stream->set_file(p_source_file);
|
||||
|
||||
Ref<VideoStreamWebm> webm_stream = Ref<VideoStreamWebm>(stream);
|
||||
|
||||
return ResourceSaver::save(p_save_path + ".webmstr", webm_stream);
|
||||
}
|
||||
|
||||
ResourceImporterWebm::ResourceImporterWebm() {
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*************************************************************************/
|
||||
/* resource_importer_webm.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* http://www.godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2017 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 RESOURCEIMPORTERWEBM_H
|
||||
#define RESOURCEIMPORTERWEBM_H
|
||||
|
||||
#include "io/resource_import.h"
|
||||
|
||||
class ResourceImporterWebm : public ResourceImporter {
|
||||
GDCLASS(ResourceImporterWebm, ResourceImporter)
|
||||
public:
|
||||
virtual String get_importer_name() const;
|
||||
virtual String get_visible_name() const;
|
||||
virtual void get_recognized_extensions(List<String> *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<ImportOption> *r_options, int p_preset = 0) const;
|
||||
virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const;
|
||||
|
||||
virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = NULL);
|
||||
|
||||
ResourceImporterWebm();
|
||||
};
|
||||
|
||||
#endif // RESOURCEIMPORTERWEBM_H
|
|
@ -35,10 +35,13 @@
|
|||
#include "mkvparser/mkvparser.h"
|
||||
|
||||
#include "os/file_access.h"
|
||||
#include "os/os.h"
|
||||
#include "project_settings.h"
|
||||
|
||||
#include "thirdparty/misc/yuv2rgb.h"
|
||||
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
class MkvReader : public mkvparser::IMkvReader {
|
||||
|
@ -47,6 +50,8 @@ public:
|
|||
MkvReader(const String &p_file) {
|
||||
|
||||
file = FileAccess::open(p_file, FileAccess::READ);
|
||||
|
||||
ERR_EXPLAIN("Failed loading resource: '" + p_file + "';");
|
||||
ERR_FAIL_COND(!file);
|
||||
}
|
||||
~MkvReader() {
|
||||
|
@ -113,14 +118,14 @@ bool VideoStreamPlaybackWebm::open_file(const String &p_file) {
|
|||
webm = memnew(WebMDemuxer(new MkvReader(file_name), 0, audio_track));
|
||||
if (webm->isOpen()) {
|
||||
|
||||
video = memnew(VPXDecoder(*webm, 8)); //TODO: Detect CPU threads
|
||||
video = memnew(VPXDecoder(*webm, OS::get_singleton()->get_processor_count()));
|
||||
if (video->isOpen()) {
|
||||
|
||||
audio = memnew(OpusVorbisDecoder(*webm));
|
||||
if (audio->isOpen()) {
|
||||
|
||||
audio_frame = memnew(WebMFrame);
|
||||
pcm = (int16_t *)memalloc(sizeof(int16_t) * audio->getBufferSamples() * webm->getChannels());
|
||||
pcm = (float *)memalloc(sizeof(float) * audio->getBufferSamples() * webm->getChannels());
|
||||
} else {
|
||||
|
||||
memdelete(audio);
|
||||
|
@ -183,7 +188,7 @@ void VideoStreamPlaybackWebm::set_paused(bool p_paused) {
|
|||
|
||||
paused = p_paused;
|
||||
}
|
||||
bool VideoStreamPlaybackWebm::is_paused(bool p_paused) const {
|
||||
bool VideoStreamPlaybackWebm::is_paused() const {
|
||||
|
||||
return paused;
|
||||
}
|
||||
|
@ -222,11 +227,18 @@ Ref<Texture> VideoStreamPlaybackWebm::get_texture() {
|
|||
|
||||
return texture;
|
||||
}
|
||||
|
||||
void VideoStreamPlaybackWebm::update(float p_delta) {
|
||||
|
||||
if ((!playing || paused) || !video)
|
||||
return;
|
||||
|
||||
time += p_delta;
|
||||
|
||||
if (time < video_pos) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool audio_buffer_full = false;
|
||||
|
||||
if (samples_offset > -1) {
|
||||
|
@ -245,13 +257,15 @@ void VideoStreamPlaybackWebm::update(float p_delta) {
|
|||
}
|
||||
|
||||
const bool hasAudio = (audio && mix_callback);
|
||||
while ((hasAudio && (!audio_buffer_full || !has_enough_video_frames())) || (!hasAudio && video_frames_pos == 0)) {
|
||||
while ((hasAudio && !audio_buffer_full && !has_enough_video_frames()) ||
|
||||
(!hasAudio && video_frames_pos == 0)) {
|
||||
|
||||
if (hasAudio && !audio_buffer_full && audio_frame->isValid() && audio->getPCMS16(*audio_frame, pcm, num_decoded_samples) && num_decoded_samples > 0) {
|
||||
if (hasAudio && !audio_buffer_full && audio_frame->isValid() &&
|
||||
audio->getPCMF(*audio_frame, pcm, num_decoded_samples) && num_decoded_samples > 0) {
|
||||
|
||||
const int mixed = mix_callback(mix_udata, pcm, num_decoded_samples);
|
||||
if (mixed != num_decoded_samples) {
|
||||
|
||||
if (mixed != num_decoded_samples) {
|
||||
samples_offset = mixed;
|
||||
audio_buffer_full = true;
|
||||
}
|
||||
|
@ -273,72 +287,61 @@ void VideoStreamPlaybackWebm::update(float p_delta) {
|
|||
++video_frames_pos;
|
||||
};
|
||||
|
||||
const double video_delay = video->getFramesDelay() * video_frame_delay;
|
||||
|
||||
bool want_this_frame = false;
|
||||
while (video_frames_pos > 0 && !want_this_frame) {
|
||||
bool video_frame_done = false;
|
||||
while (video_frames_pos > 0 && !video_frame_done) {
|
||||
|
||||
WebMFrame *video_frame = video_frames[0];
|
||||
if (video_frame->time <= time + video_delay) {
|
||||
|
||||
if (video->decode(*video_frame)) {
|
||||
// It seems VPXDecoder::decode has to be executed even though we might skip this frame
|
||||
if (video->decode(*video_frame)) {
|
||||
|
||||
VPXDecoder::IMAGE_ERROR err;
|
||||
VPXDecoder::Image image;
|
||||
VPXDecoder::IMAGE_ERROR err;
|
||||
VPXDecoder::Image image;
|
||||
|
||||
while ((err = video->getImage(image)) != VPXDecoder::NO_FRAME) {
|
||||
if (should_process(*video_frame)) {
|
||||
|
||||
want_this_frame = (time - video_frame->time <= video_frame_delay);
|
||||
if ((err = video->getImage(image)) != VPXDecoder::NO_FRAME) {
|
||||
|
||||
if (want_this_frame) {
|
||||
if (err == VPXDecoder::NO_ERROR && image.w == webm->getWidth() && image.h == webm->getHeight()) {
|
||||
|
||||
if (err == VPXDecoder::NO_ERROR && image.w == webm->getWidth() && image.h == webm->getHeight()) {
|
||||
PoolVector<uint8_t>::Write w = frame_data.write();
|
||||
bool converted = false;
|
||||
|
||||
PoolVector<uint8_t>::Write w = frame_data.write();
|
||||
bool converted = false;
|
||||
if (image.chromaShiftW == 1 && image.chromaShiftH == 1) {
|
||||
|
||||
if (image.chromaShiftW == 1 && image.chromaShiftH == 1) {
|
||||
yuv420_2_rgb8888(w.ptr(), image.planes[0], image.planes[2], image.planes[1], image.w, image.h, image.linesize[0], image.linesize[1], image.w << 2, 0);
|
||||
// libyuv::I420ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h);
|
||||
converted = true;
|
||||
} else if (image.chromaShiftW == 1 && image.chromaShiftH == 0) {
|
||||
|
||||
yuv420_2_rgb8888(w.ptr(), image.planes[0], image.planes[2], image.planes[1], image.w, image.h, image.linesize[0], image.linesize[1], image.w << 2, 0);
|
||||
// libyuv::I420ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h);
|
||||
converted = true;
|
||||
} else if (image.chromaShiftW == 1 && image.chromaShiftH == 0) {
|
||||
yuv422_2_rgb8888(w.ptr(), image.planes[0], image.planes[2], image.planes[1], image.w, image.h, image.linesize[0], image.linesize[1], image.w << 2, 0);
|
||||
// libyuv::I422ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h);
|
||||
converted = true;
|
||||
} else if (image.chromaShiftW == 0 && image.chromaShiftH == 0) {
|
||||
|
||||
yuv422_2_rgb8888(w.ptr(), image.planes[0], image.planes[2], image.planes[1], image.w, image.h, image.linesize[0], image.linesize[1], image.w << 2, 0);
|
||||
// libyuv::I422ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h);
|
||||
converted = true;
|
||||
} else if (image.chromaShiftW == 0 && image.chromaShiftH == 0) {
|
||||
yuv444_2_rgb8888(w.ptr(), image.planes[0], image.planes[2], image.planes[1], image.w, image.h, image.linesize[0], image.linesize[1], image.w << 2, 0);
|
||||
// libyuv::I444ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h);
|
||||
converted = true;
|
||||
} else if (image.chromaShiftW == 2 && image.chromaShiftH == 0) {
|
||||
|
||||
yuv444_2_rgb8888(w.ptr(), image.planes[0], image.planes[2], image.planes[1], image.w, image.h, image.linesize[0], image.linesize[1], image.w << 2, 0);
|
||||
// libyuv::I444ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h);
|
||||
converted = true;
|
||||
} else if (image.chromaShiftW == 2 && image.chromaShiftH == 0) {
|
||||
|
||||
// libyuv::I411ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h);
|
||||
// converted = true;
|
||||
}
|
||||
|
||||
if (converted)
|
||||
texture->set_data(Image(image.w, image.h, 0, Image::FORMAT_RGBA8, frame_data)); //Zero copy send to visual server
|
||||
// libyuv::I411ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h);
|
||||
// converted = true;
|
||||
}
|
||||
|
||||
break;
|
||||
if (converted) {
|
||||
Ref<Image> img = memnew(Image(image.w, image.h, 0, Image::FORMAT_RGBA8, frame_data));
|
||||
texture->set_data(img); //Zero copy send to visual server
|
||||
video_frame_done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
video_frame_delay = video_frame->time - video_pos;
|
||||
video_pos = video_frame->time;
|
||||
|
||||
memmove(video_frames, video_frames + 1, (--video_frames_pos) * sizeof(void *));
|
||||
video_frames[video_frames_pos] = video_frame;
|
||||
} else {
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
time += p_delta;
|
||||
video_pos = video_frame->time;
|
||||
memmove(video_frames, video_frames + 1, (--video_frames_pos) * sizeof(void *));
|
||||
video_frames[video_frames_pos] = video_frame;
|
||||
}
|
||||
|
||||
if (video_frames_pos == 0 && webm->isEOS())
|
||||
stop();
|
||||
|
@ -372,6 +375,11 @@ inline bool VideoStreamPlaybackWebm::has_enough_video_frames() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool VideoStreamPlaybackWebm::should_process(WebMFrame &video_frame) {
|
||||
const double audio_delay = AudioServer::get_singleton()->get_output_delay();
|
||||
return video_frame.time >= time + audio_delay + delay_compensation;
|
||||
}
|
||||
|
||||
void VideoStreamPlaybackWebm::delete_pointers() {
|
||||
|
||||
if (pcm)
|
||||
|
@ -395,34 +403,6 @@ void VideoStreamPlaybackWebm::delete_pointers() {
|
|||
|
||||
/**/
|
||||
|
||||
RES ResourceFormatLoaderVideoStreamWebm::load(const String &p_path, const String &p_original_path, Error *r_error) {
|
||||
|
||||
Ref<VideoStreamWebm> stream = memnew(VideoStreamWebm);
|
||||
stream->set_file(p_path);
|
||||
if (r_error)
|
||||
*r_error = OK;
|
||||
return stream;
|
||||
}
|
||||
|
||||
void ResourceFormatLoaderVideoStreamWebm::get_recognized_extensions(List<String> *p_extensions) const {
|
||||
|
||||
p_extensions->push_back("webm");
|
||||
}
|
||||
bool ResourceFormatLoaderVideoStreamWebm::handles_type(const String &p_type) const {
|
||||
|
||||
return (p_type == "VideoStream" || p_type == "VideoStreamWebm");
|
||||
}
|
||||
|
||||
String ResourceFormatLoaderVideoStreamWebm::get_resource_type(const String &p_path) const {
|
||||
|
||||
const String exl = p_path.get_extension().to_lower();
|
||||
if (exl == "webm")
|
||||
return "VideoStreamWebm";
|
||||
return "";
|
||||
}
|
||||
|
||||
/**/
|
||||
|
||||
VideoStreamWebm::VideoStreamWebm()
|
||||
: audio_track(0) {}
|
||||
|
||||
|
@ -439,6 +419,19 @@ void VideoStreamWebm::set_file(const String &p_file) {
|
|||
|
||||
file = p_file;
|
||||
}
|
||||
String VideoStreamWebm::get_file() {
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
void VideoStreamWebm::_bind_methods() {
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_file", "file"), &VideoStreamWebm::set_file);
|
||||
ClassDB::bind_method(D_METHOD("get_file"), &VideoStreamWebm::get_file);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "file", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_file", "get_file");
|
||||
}
|
||||
|
||||
void VideoStreamWebm::set_audio_track(int p_track) {
|
||||
|
||||
audio_track = p_track;
|
||||
|
|
|
@ -60,7 +60,7 @@ class VideoStreamPlaybackWebm : public VideoStreamPlayback {
|
|||
PoolVector<uint8_t> frame_data;
|
||||
Ref<ImageTexture> texture;
|
||||
|
||||
int16_t *pcm;
|
||||
float *pcm;
|
||||
|
||||
public:
|
||||
VideoStreamPlaybackWebm();
|
||||
|
@ -74,7 +74,7 @@ public:
|
|||
virtual bool is_playing() const;
|
||||
|
||||
virtual void set_paused(bool p_paused);
|
||||
virtual bool is_paused(bool p_paused) const;
|
||||
virtual bool is_paused() const;
|
||||
|
||||
virtual void set_loop(bool p_enable);
|
||||
virtual bool has_loop() const;
|
||||
|
@ -95,6 +95,7 @@ public:
|
|||
|
||||
private:
|
||||
inline bool has_enough_video_frames() const;
|
||||
bool should_process(WebMFrame &video_frame);
|
||||
|
||||
void delete_pointers();
|
||||
};
|
||||
|
@ -103,27 +104,21 @@ private:
|
|||
|
||||
class VideoStreamWebm : public VideoStream {
|
||||
|
||||
GDCLASS(VideoStreamWebm, VideoStream)
|
||||
GDCLASS(VideoStreamWebm, VideoStream);
|
||||
RES_BASE_EXTENSION("webmstr");
|
||||
|
||||
String file;
|
||||
int audio_track;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
VideoStreamWebm();
|
||||
|
||||
virtual Ref<VideoStreamPlayback> instance_playback();
|
||||
|
||||
virtual void set_file(const String &p_file);
|
||||
String get_file();
|
||||
virtual void set_audio_track(int p_track);
|
||||
};
|
||||
|
||||
/**/
|
||||
|
||||
class ResourceFormatLoaderVideoStreamWebm : public ResourceFormatLoader {
|
||||
|
||||
public:
|
||||
virtual RES load(const String &p_path, const String &p_original_path, Error *r_error);
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const;
|
||||
virtual bool handles_type(const String &p_type) const;
|
||||
virtual String get_resource_type(const String &p_path) const;
|
||||
};
|
||||
|
|
|
@ -42,44 +42,127 @@ void VideoPlayer::sp_set_mix_rate(int p_rate) {
|
|||
server_mix_rate = p_rate;
|
||||
}
|
||||
|
||||
bool VideoPlayer::sp_mix(int32_t *p_buffer, int p_frames) {
|
||||
bool VideoPlayer::mix(AudioFrame *p_buffer, int p_frames) {
|
||||
|
||||
if (resampler.is_ready()) {
|
||||
// Check the amount resampler can really handle.
|
||||
// If it cannot, wait "wait_resampler_phase_limit" times.
|
||||
// This mechanism contributes to smoother pause/unpause operation.
|
||||
if (p_frames <= resampler.get_num_of_ready_frames() ||
|
||||
wait_resampler_limit <= wait_resampler) {
|
||||
wait_resampler = 0;
|
||||
return resampler.mix(p_buffer, p_frames);
|
||||
}
|
||||
|
||||
wait_resampler++;
|
||||
return false;
|
||||
}
|
||||
|
||||
int VideoPlayer::_audio_mix_callback(void *p_udata, const int16_t *p_data, int p_frames) {
|
||||
// Called from main thread (eg VideoStreamPlaybackWebm::update)
|
||||
int VideoPlayer::_audio_mix_callback(void *p_udata, const float *p_data, int p_frames) {
|
||||
|
||||
VideoPlayer *vp = (VideoPlayer *)p_udata;
|
||||
|
||||
int todo = MIN(vp->resampler.get_todo(), p_frames);
|
||||
int todo = MIN(vp->resampler.get_writer_space(), p_frames);
|
||||
|
||||
int16_t *wb = vp->resampler.get_write_buffer();
|
||||
float *wb = vp->resampler.get_write_buffer();
|
||||
int c = vp->resampler.get_channel_count();
|
||||
|
||||
for (int i = 0; i < todo * c; i++) {
|
||||
wb[i] = p_data[i];
|
||||
}
|
||||
vp->resampler.write(todo);
|
||||
|
||||
return todo;
|
||||
}
|
||||
|
||||
// Called from audio thread
|
||||
void VideoPlayer::_mix_audio() {
|
||||
|
||||
if (!stream.is_valid()) {
|
||||
return;
|
||||
}
|
||||
if (!playback.is_valid() || !playback->is_playing() || playback->is_paused()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AudioFrame *buffer = mix_buffer.ptr();
|
||||
int buffer_size = mix_buffer.size();
|
||||
|
||||
// Resample
|
||||
if (!mix(buffer, buffer_size))
|
||||
return;
|
||||
|
||||
AudioFrame vol = AudioFrame(volume, volume);
|
||||
|
||||
// Copy to server's audio buffer
|
||||
switch (AudioServer::get_singleton()->get_speaker_mode()) {
|
||||
|
||||
case AudioServer::SPEAKER_MODE_STEREO: {
|
||||
AudioFrame *target = AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 0);
|
||||
|
||||
for (int j = 0; j < buffer_size; j++) {
|
||||
|
||||
target[j] += buffer[j] * vol;
|
||||
}
|
||||
|
||||
} break;
|
||||
case AudioServer::SPEAKER_SURROUND_51: {
|
||||
|
||||
AudioFrame *targets[2] = {
|
||||
AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 1),
|
||||
AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 2),
|
||||
};
|
||||
|
||||
for (int j = 0; j < buffer_size; j++) {
|
||||
|
||||
AudioFrame frame = buffer[j] * vol;
|
||||
targets[0][j] = frame;
|
||||
targets[1][j] = frame;
|
||||
}
|
||||
} break;
|
||||
case AudioServer::SPEAKER_SURROUND_71: {
|
||||
|
||||
AudioFrame *targets[3] = {
|
||||
AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 1),
|
||||
AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 2),
|
||||
AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 3)
|
||||
};
|
||||
|
||||
for (int j = 0; j < buffer_size; j++) {
|
||||
|
||||
AudioFrame frame = buffer[j] * vol;
|
||||
targets[0][j] += frame;
|
||||
targets[1][j] += frame;
|
||||
targets[2][j] += frame;
|
||||
}
|
||||
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoPlayer::_notification(int p_notification) {
|
||||
|
||||
switch (p_notification) {
|
||||
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
|
||||
AudioServer::get_singleton()->add_callback(_mix_audios, this);
|
||||
|
||||
if (stream.is_valid() && autoplay && !Engine::get_singleton()->is_editor_hint()) {
|
||||
play();
|
||||
}
|
||||
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
|
||||
AudioServer::get_singleton()->remove_callback(_mix_audios, this);
|
||||
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_INTERNAL_PROCESS: {
|
||||
|
||||
bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus);
|
||||
|
||||
if (stream.is_null())
|
||||
return;
|
||||
if (paused)
|
||||
|
@ -87,10 +170,11 @@ void VideoPlayer::_notification(int p_notification) {
|
|||
if (!playback->is_playing())
|
||||
return;
|
||||
|
||||
double audio_time = USEC_TO_SEC(OS::get_singleton()->get_ticks_usec()); //AudioServer::get_singleton()->get_mix_time();
|
||||
double audio_time = USEC_TO_SEC(OS::get_singleton()->get_ticks_usec());
|
||||
|
||||
double delta = last_audio_time == 0 ? 0 : audio_time - last_audio_time;
|
||||
last_audio_time = audio_time;
|
||||
|
||||
if (delta == 0)
|
||||
return;
|
||||
|
||||
|
@ -135,6 +219,9 @@ bool VideoPlayer::has_expand() const {
|
|||
void VideoPlayer::set_stream(const Ref<VideoStream> &p_stream) {
|
||||
|
||||
stop();
|
||||
AudioServer::get_singleton()->lock();
|
||||
mix_buffer.resize(AudioServer::get_singleton()->thread_get_mix_buffer_size());
|
||||
AudioServer::get_singleton()->unlock();
|
||||
|
||||
stream = p_stream;
|
||||
if (stream.is_valid()) {
|
||||
|
@ -309,6 +396,40 @@ bool VideoPlayer::has_autoplay() const {
|
|||
return autoplay;
|
||||
};
|
||||
|
||||
void VideoPlayer::set_bus(const StringName &p_bus) {
|
||||
|
||||
//if audio is active, must lock this
|
||||
AudioServer::get_singleton()->lock();
|
||||
bus = p_bus;
|
||||
AudioServer::get_singleton()->unlock();
|
||||
}
|
||||
|
||||
StringName VideoPlayer::get_bus() const {
|
||||
|
||||
for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) {
|
||||
if (AudioServer::get_singleton()->get_bus_name(i) == bus) {
|
||||
return bus;
|
||||
}
|
||||
}
|
||||
return "Master";
|
||||
}
|
||||
|
||||
void VideoPlayer::_validate_property(PropertyInfo &property) const {
|
||||
|
||||
if (property.name == "bus") {
|
||||
|
||||
String options;
|
||||
for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) {
|
||||
if (i > 0)
|
||||
options += ",";
|
||||
String name = AudioServer::get_singleton()->get_bus_name(i);
|
||||
options += name;
|
||||
}
|
||||
|
||||
property.hint_string = options;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoPlayer::_bind_methods() {
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_stream", "stream"), &VideoPlayer::set_stream);
|
||||
|
@ -345,6 +466,9 @@ void VideoPlayer::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_buffering_msec", "msec"), &VideoPlayer::set_buffering_msec);
|
||||
ClassDB::bind_method(D_METHOD("get_buffering_msec"), &VideoPlayer::get_buffering_msec);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_bus", "bus"), &VideoPlayer::set_bus);
|
||||
ClassDB::bind_method(D_METHOD("get_bus"), &VideoPlayer::get_bus);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_video_texture"), &VideoPlayer::get_video_texture);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_track", PROPERTY_HINT_RANGE, "0,128,1"), "set_audio_track", "get_audio_track");
|
||||
|
@ -354,6 +478,7 @@ void VideoPlayer::_bind_methods() {
|
|||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "has_autoplay");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "paused"), "set_paused", "is_paused");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand"), "set_expand", "has_expand");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus");
|
||||
}
|
||||
|
||||
VideoPlayer::VideoPlayer() {
|
||||
|
@ -372,6 +497,9 @@ VideoPlayer::VideoPlayer() {
|
|||
// internal_stream.player=this;
|
||||
// stream_rid=AudioServer::get_singleton()->audio_stream_create(&internal_stream);
|
||||
last_audio_time = 0;
|
||||
|
||||
wait_resampler = 0;
|
||||
wait_resampler_limit = 2;
|
||||
};
|
||||
|
||||
VideoPlayer::~VideoPlayer() {
|
||||
|
|
|
@ -33,17 +33,24 @@
|
|||
#include "scene/gui/control.h"
|
||||
#include "scene/resources/video_stream.h"
|
||||
#include "servers/audio/audio_rb_resampler.h"
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
class VideoPlayer : public Control {
|
||||
|
||||
GDCLASS(VideoPlayer, Control);
|
||||
|
||||
struct Output {
|
||||
|
||||
AudioFrame vol;
|
||||
int bus_index;
|
||||
Viewport *viewport; //pointer only used for reference to previous mix
|
||||
};
|
||||
Ref<VideoStreamPlayback> playback;
|
||||
Ref<VideoStream> stream;
|
||||
|
||||
int sp_get_channel_count() const;
|
||||
void sp_set_mix_rate(int p_rate); //notify the stream of the mix rate
|
||||
bool sp_mix(int32_t *p_buffer, int p_frames);
|
||||
bool mix(AudioFrame *p_buffer, int p_frames);
|
||||
|
||||
RID stream_rid;
|
||||
|
||||
|
@ -51,6 +58,8 @@ class VideoPlayer : public Control {
|
|||
Ref<Image> last_frame;
|
||||
|
||||
AudioRBResampler resampler;
|
||||
Vector<AudioFrame> mix_buffer;
|
||||
int wait_resampler, wait_resampler_limit;
|
||||
|
||||
bool paused;
|
||||
bool autoplay;
|
||||
|
@ -61,12 +70,18 @@ class VideoPlayer : public Control {
|
|||
int buffering_ms;
|
||||
int server_mix_rate;
|
||||
int audio_track;
|
||||
int bus_index;
|
||||
|
||||
static int _audio_mix_callback(void *p_udata, const int16_t *p_data, int p_frames);
|
||||
StringName bus;
|
||||
|
||||
void _mix_audio();
|
||||
static int _audio_mix_callback(void *p_udata, const float *p_data, int p_frames);
|
||||
static void _mix_audios(void *self) { reinterpret_cast<VideoPlayer *>(self)->_mix_audio(); }
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_notification);
|
||||
void _validate_property(PropertyInfo &property) const;
|
||||
|
||||
public:
|
||||
Size2 get_minimum_size() const;
|
||||
|
@ -104,6 +119,9 @@ public:
|
|||
void set_buffering_msec(int p_msec);
|
||||
int get_buffering_msec() const;
|
||||
|
||||
void set_bus(const StringName &p_bus);
|
||||
StringName get_bus() const;
|
||||
|
||||
VideoPlayer();
|
||||
~VideoPlayer();
|
||||
};
|
||||
|
|
|
@ -40,7 +40,7 @@ protected:
|
|||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
typedef int (*AudioMixCallback)(void *p_udata, const int16_t *p_data, int p_frames);
|
||||
typedef int (*AudioMixCallback)(void *p_udata, const float *p_data, int p_frames);
|
||||
|
||||
virtual void stop() = 0;
|
||||
virtual void play() = 0;
|
||||
|
@ -48,7 +48,7 @@ public:
|
|||
virtual bool is_playing() const = 0;
|
||||
|
||||
virtual void set_paused(bool p_paused) = 0;
|
||||
virtual bool is_paused(bool p_paused) const = 0;
|
||||
virtual bool is_paused() const = 0;
|
||||
|
||||
virtual void set_loop(bool p_enable) = 0;
|
||||
virtual bool has_loop() const = 0;
|
||||
|
|
|
@ -28,6 +28,9 @@
|
|||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
#include "audio_rb_resampler.h"
|
||||
#include "core/math/math_funcs.h"
|
||||
#include "os/os.h"
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
int AudioRBResampler::get_channel_count() const {
|
||||
|
||||
|
@ -37,8 +40,11 @@ int AudioRBResampler::get_channel_count() const {
|
|||
return channels;
|
||||
}
|
||||
|
||||
// Linear interpolation based sample rate convertion (low quality)
|
||||
// Note that AudioStreamPlaybackResampled::mix has better algorithm,
|
||||
// but it wasn't obvious to integrate that with VideoPlayer
|
||||
template <int C>
|
||||
uint32_t AudioRBResampler::_resample(int32_t *p_dest, int p_todo, int32_t p_increment) {
|
||||
uint32_t AudioRBResampler::_resample(AudioFrame *p_dest, int p_todo, int32_t p_increment) {
|
||||
|
||||
uint32_t read = offset & MIX_FRAC_MASK;
|
||||
|
||||
|
@ -47,186 +53,128 @@ uint32_t AudioRBResampler::_resample(int32_t *p_dest, int p_todo, int32_t p_incr
|
|||
offset = (offset + p_increment) & (((1 << (rb_bits + MIX_FRAC_BITS)) - 1));
|
||||
read += p_increment;
|
||||
uint32_t pos = offset >> MIX_FRAC_BITS;
|
||||
uint32_t frac = offset & MIX_FRAC_MASK;
|
||||
#ifndef FAST_AUDIO
|
||||
float frac = float(offset & MIX_FRAC_MASK) / float(MIX_FRAC_LEN);
|
||||
ERR_FAIL_COND_V(pos >= rb_len, 0);
|
||||
#endif
|
||||
uint32_t pos_next = (pos + 1) & rb_mask;
|
||||
//printf("rb pos %i\n",pos);
|
||||
|
||||
// since this is a template with a known compile time value (C), conditionals go away when compiling.
|
||||
if (C == 1) {
|
||||
|
||||
int32_t v0 = rb[pos];
|
||||
int32_t v0n = rb[pos_next];
|
||||
#ifndef FAST_AUDIO
|
||||
v0 += (v0n - v0) * (int32_t)frac >> MIX_FRAC_BITS;
|
||||
#endif
|
||||
v0 <<= 16;
|
||||
p_dest[i] = v0;
|
||||
float v0 = rb[pos];
|
||||
float v0n = rb[pos_next];
|
||||
v0 += (v0n - v0) * frac;
|
||||
p_dest[i] = AudioFrame(v0, v0);
|
||||
}
|
||||
|
||||
if (C == 2) {
|
||||
|
||||
int32_t v0 = rb[(pos << 1) + 0];
|
||||
int32_t v1 = rb[(pos << 1) + 1];
|
||||
int32_t v0n = rb[(pos_next << 1) + 0];
|
||||
int32_t v1n = rb[(pos_next << 1) + 1];
|
||||
float v0 = rb[(pos << 1) + 0];
|
||||
float v1 = rb[(pos << 1) + 1];
|
||||
float v0n = rb[(pos_next << 1) + 0];
|
||||
float v1n = rb[(pos_next << 1) + 1];
|
||||
|
||||
#ifndef FAST_AUDIO
|
||||
v0 += (v0n - v0) * (int32_t)frac >> MIX_FRAC_BITS;
|
||||
v1 += (v1n - v1) * (int32_t)frac >> MIX_FRAC_BITS;
|
||||
#endif
|
||||
v0 <<= 16;
|
||||
v1 <<= 16;
|
||||
p_dest[(i << 1) + 0] = v0;
|
||||
p_dest[(i << 1) + 1] = v1;
|
||||
v0 += (v0n - v0) * frac;
|
||||
v1 += (v1n - v1) * frac;
|
||||
p_dest[i] = AudioFrame(v0, v1);
|
||||
}
|
||||
|
||||
// For now, channels higher than stereo are almost ignored
|
||||
if (C == 4) {
|
||||
|
||||
int32_t v0 = rb[(pos << 2) + 0];
|
||||
int32_t v1 = rb[(pos << 2) + 1];
|
||||
int32_t v2 = rb[(pos << 2) + 2];
|
||||
int32_t v3 = rb[(pos << 2) + 3];
|
||||
int32_t v0n = rb[(pos_next << 2) + 0];
|
||||
int32_t v1n = rb[(pos_next << 2) + 1];
|
||||
int32_t v2n = rb[(pos_next << 2) + 2];
|
||||
int32_t v3n = rb[(pos_next << 2) + 3];
|
||||
float v0 = rb[(pos << 2) + 0];
|
||||
float v1 = rb[(pos << 2) + 1];
|
||||
float v2 = rb[(pos << 2) + 2];
|
||||
float v3 = rb[(pos << 2) + 3];
|
||||
float v0n = rb[(pos_next << 2) + 0];
|
||||
float v1n = rb[(pos_next << 2) + 1];
|
||||
float v2n = rb[(pos_next << 2) + 2];
|
||||
float v3n = rb[(pos_next << 2) + 3];
|
||||
|
||||
#ifndef FAST_AUDIO
|
||||
v0 += (v0n - v0) * (int32_t)frac >> MIX_FRAC_BITS;
|
||||
v1 += (v1n - v1) * (int32_t)frac >> MIX_FRAC_BITS;
|
||||
v2 += (v2n - v2) * (int32_t)frac >> MIX_FRAC_BITS;
|
||||
v3 += (v3n - v3) * (int32_t)frac >> MIX_FRAC_BITS;
|
||||
#endif
|
||||
v0 <<= 16;
|
||||
v1 <<= 16;
|
||||
v2 <<= 16;
|
||||
v3 <<= 16;
|
||||
p_dest[(i << 2) + 0] = v0;
|
||||
p_dest[(i << 2) + 1] = v1;
|
||||
p_dest[(i << 2) + 2] = v2;
|
||||
p_dest[(i << 2) + 3] = v3;
|
||||
v0 += (v0n - v0) * frac;
|
||||
v1 += (v1n - v1) * frac;
|
||||
v2 += (v2n - v2) * frac;
|
||||
v3 += (v3n - v3) * frac;
|
||||
p_dest[i] = AudioFrame(v0, v1);
|
||||
}
|
||||
|
||||
if (C == 6) {
|
||||
|
||||
int32_t v0 = rb[(pos * 6) + 0];
|
||||
int32_t v1 = rb[(pos * 6) + 1];
|
||||
int32_t v2 = rb[(pos * 6) + 2];
|
||||
int32_t v3 = rb[(pos * 6) + 3];
|
||||
int32_t v4 = rb[(pos * 6) + 4];
|
||||
int32_t v5 = rb[(pos * 6) + 5];
|
||||
int32_t v0n = rb[(pos_next * 6) + 0];
|
||||
int32_t v1n = rb[(pos_next * 6) + 1];
|
||||
int32_t v2n = rb[(pos_next * 6) + 2];
|
||||
int32_t v3n = rb[(pos_next * 6) + 3];
|
||||
int32_t v4n = rb[(pos_next * 6) + 4];
|
||||
int32_t v5n = rb[(pos_next * 6) + 5];
|
||||
float v0 = rb[(pos * 6) + 0];
|
||||
float v1 = rb[(pos * 6) + 1];
|
||||
float v2 = rb[(pos * 6) + 2];
|
||||
float v3 = rb[(pos * 6) + 3];
|
||||
float v4 = rb[(pos * 6) + 4];
|
||||
float v5 = rb[(pos * 6) + 5];
|
||||
float v0n = rb[(pos_next * 6) + 0];
|
||||
float v1n = rb[(pos_next * 6) + 1];
|
||||
float v2n = rb[(pos_next * 6) + 2];
|
||||
float v3n = rb[(pos_next * 6) + 3];
|
||||
float v4n = rb[(pos_next * 6) + 4];
|
||||
float v5n = rb[(pos_next * 6) + 5];
|
||||
|
||||
#ifndef FAST_AUDIO
|
||||
v0 += (v0n - v0) * (int32_t)frac >> MIX_FRAC_BITS;
|
||||
v1 += (v1n - v1) * (int32_t)frac >> MIX_FRAC_BITS;
|
||||
v2 += (v2n - v2) * (int32_t)frac >> MIX_FRAC_BITS;
|
||||
v3 += (v3n - v3) * (int32_t)frac >> MIX_FRAC_BITS;
|
||||
v4 += (v4n - v4) * (int32_t)frac >> MIX_FRAC_BITS;
|
||||
v5 += (v5n - v5) * (int32_t)frac >> MIX_FRAC_BITS;
|
||||
#endif
|
||||
v0 <<= 16;
|
||||
v1 <<= 16;
|
||||
v2 <<= 16;
|
||||
v3 <<= 16;
|
||||
v4 <<= 16;
|
||||
v5 <<= 16;
|
||||
p_dest[(i * 6) + 0] = v0;
|
||||
p_dest[(i * 6) + 1] = v1;
|
||||
p_dest[(i * 6) + 2] = v2;
|
||||
p_dest[(i * 6) + 3] = v3;
|
||||
p_dest[(i * 6) + 4] = v4;
|
||||
p_dest[(i * 6) + 5] = v5;
|
||||
p_dest[i] = AudioFrame(v0, v1);
|
||||
}
|
||||
}
|
||||
|
||||
return read >> MIX_FRAC_BITS; //rb_read_pos=offset>>MIX_FRAC_BITS;
|
||||
return read >> MIX_FRAC_BITS; //rb_read_pos = offset >> MIX_FRAC_BITS;
|
||||
}
|
||||
|
||||
bool AudioRBResampler::mix(int32_t *p_dest, int p_frames) {
|
||||
bool AudioRBResampler::mix(AudioFrame *p_dest, int p_frames) {
|
||||
|
||||
if (!rb)
|
||||
return false;
|
||||
|
||||
int write_pos_cache = rb_write_pos;
|
||||
|
||||
int32_t increment = (src_mix_rate * MIX_FRAC_LEN) / target_mix_rate;
|
||||
|
||||
int rb_todo;
|
||||
|
||||
if (write_pos_cache == rb_read_pos) {
|
||||
return false; //out of buffer
|
||||
|
||||
} else if (rb_read_pos < write_pos_cache) {
|
||||
|
||||
rb_todo = write_pos_cache - rb_read_pos; //-1?
|
||||
} else {
|
||||
|
||||
rb_todo = (rb_len - rb_read_pos) + write_pos_cache; //-1?
|
||||
}
|
||||
|
||||
int todo = MIN(((int64_t(rb_todo) << MIX_FRAC_BITS) / increment) + 1, p_frames);
|
||||
int read_space = get_reader_space();
|
||||
int target_todo = MIN(get_num_of_ready_frames(), p_frames);
|
||||
|
||||
{
|
||||
|
||||
int read = 0;
|
||||
int src_read = 0;
|
||||
switch (channels) {
|
||||
case 1: read = _resample<1>(p_dest, todo, increment); break;
|
||||
case 2: read = _resample<2>(p_dest, todo, increment); break;
|
||||
case 4: read = _resample<4>(p_dest, todo, increment); break;
|
||||
case 6: read = _resample<6>(p_dest, todo, increment); break;
|
||||
case 1: src_read = _resample<1>(p_dest, target_todo, increment); break;
|
||||
case 2: src_read = _resample<2>(p_dest, target_todo, increment); break;
|
||||
case 4: src_read = _resample<4>(p_dest, target_todo, increment); break;
|
||||
case 6: src_read = _resample<6>(p_dest, target_todo, increment); break;
|
||||
}
|
||||
|
||||
//end of stream, fadeout
|
||||
int remaining = p_frames - todo;
|
||||
if (remaining && todo > 0) {
|
||||
if (src_read > read_space)
|
||||
src_read = read_space;
|
||||
|
||||
//print_line("fadeout");
|
||||
for (uint32_t c = 0; c < channels; c++) {
|
||||
rb_read_pos = (rb_read_pos + src_read) & rb_mask;
|
||||
|
||||
for (int i = 0; i < todo; i++) {
|
||||
|
||||
int32_t samp = p_dest[i * channels + c] >> 8;
|
||||
uint32_t mul = (todo - i) * 256 / todo;
|
||||
//print_line("mul: "+itos(i)+" "+itos(mul));
|
||||
p_dest[i * channels + c] = samp * mul;
|
||||
}
|
||||
// Create fadeout effect for the end of stream (note that it can be because of slow writer)
|
||||
if (p_frames - target_todo > 0) {
|
||||
for (int i = 0; i < target_todo; i++) {
|
||||
p_dest[i] = p_dest[i] * float(target_todo - i) / float(target_todo);
|
||||
}
|
||||
}
|
||||
|
||||
//zero out what remains there to avoid glitches
|
||||
for (uint32_t i = todo * channels; i < int(p_frames) * channels; i++) {
|
||||
|
||||
p_dest[i] = 0;
|
||||
// Fill zeros (silence) for the rest of frames
|
||||
for (uint32_t i = target_todo; i < p_frames; i++) {
|
||||
p_dest[i] = AudioFrame(0, 0);
|
||||
}
|
||||
|
||||
if (read > rb_todo)
|
||||
read = rb_todo;
|
||||
|
||||
rb_read_pos = (rb_read_pos + read) & rb_mask;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int AudioRBResampler::get_num_of_ready_frames() {
|
||||
if (!is_ready())
|
||||
return 0;
|
||||
int32_t increment = (src_mix_rate * MIX_FRAC_LEN) / target_mix_rate;
|
||||
int read_space = get_reader_space();
|
||||
return (int64_t(read_space) << MIX_FRAC_BITS) / increment;
|
||||
}
|
||||
|
||||
Error AudioRBResampler::setup(int p_channels, int p_src_mix_rate, int p_target_mix_rate, int p_buffer_msec, int p_minbuff_needed) {
|
||||
|
||||
ERR_FAIL_COND_V(p_channels != 1 && p_channels != 2 && p_channels != 4 && p_channels != 6, ERR_INVALID_PARAMETER);
|
||||
|
||||
//float buffering_sec = int(GLOBAL_DEF("audio/stream_buffering_ms",500))/1000.0;
|
||||
int desired_rb_bits = nearest_shift(MAX((p_buffer_msec / 1000.0) * p_src_mix_rate, p_minbuff_needed));
|
||||
|
||||
bool recreate = !rb;
|
||||
|
||||
if (rb && (uint32_t(desired_rb_bits) != rb_bits || channels != uint32_t(p_channels))) {
|
||||
//recreate
|
||||
|
||||
memdelete_arr(rb);
|
||||
memdelete_arr(read_buf);
|
||||
|
@ -239,8 +187,8 @@ Error AudioRBResampler::setup(int p_channels, int p_src_mix_rate, int p_target_m
|
|||
rb_bits = desired_rb_bits;
|
||||
rb_len = (1 << rb_bits);
|
||||
rb_mask = rb_len - 1;
|
||||
rb = memnew_arr(int16_t, rb_len * p_channels);
|
||||
read_buf = memnew_arr(int16_t, rb_len * p_channels);
|
||||
rb = memnew_arr(float, rb_len *p_channels);
|
||||
read_buf = memnew_arr(float, rb_len *p_channels);
|
||||
}
|
||||
|
||||
src_mix_rate = p_src_mix_rate;
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#define AUDIO_RB_RESAMPLER_H
|
||||
|
||||
#include "os/memory.h"
|
||||
#include "servers/audio_server.h"
|
||||
#include "typedefs.h"
|
||||
|
||||
struct AudioRBResampler {
|
||||
|
@ -53,11 +54,11 @@ struct AudioRBResampler {
|
|||
MIX_FRAC_MASK = MIX_FRAC_LEN - 1,
|
||||
};
|
||||
|
||||
int16_t *read_buf;
|
||||
int16_t *rb;
|
||||
float *read_buf;
|
||||
float *rb;
|
||||
|
||||
template <int C>
|
||||
uint32_t _resample(int32_t *p_dest, int p_todo, int32_t p_increment);
|
||||
uint32_t _resample(AudioFrame *p_dest, int p_todo, int32_t p_increment);
|
||||
|
||||
public:
|
||||
_FORCE_INLINE_ void flush() {
|
||||
|
@ -71,33 +72,48 @@ public:
|
|||
}
|
||||
|
||||
_FORCE_INLINE_ int get_total() const {
|
||||
|
||||
return rb_len - 1;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ int get_todo() const { //return amount of frames to mix
|
||||
_FORCE_INLINE_ int get_writer_space() const {
|
||||
int space, r, w;
|
||||
|
||||
int todo;
|
||||
int read_pos_cache = rb_read_pos;
|
||||
r = rb_read_pos;
|
||||
w = rb_write_pos;
|
||||
|
||||
if (read_pos_cache == rb_write_pos) {
|
||||
todo = rb_len - 1;
|
||||
} else if (read_pos_cache > rb_write_pos) {
|
||||
|
||||
todo = read_pos_cache - rb_write_pos - 1;
|
||||
if (r == w) {
|
||||
space = rb_len - 1;
|
||||
} else if (w < r) {
|
||||
space = r - w - 1;
|
||||
} else {
|
||||
|
||||
todo = (rb_len - rb_write_pos) + read_pos_cache - 1;
|
||||
space = (rb_len - r) + w - 1;
|
||||
}
|
||||
|
||||
return todo;
|
||||
return space;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ int get_reader_space() const {
|
||||
int space, r, w;
|
||||
|
||||
r = rb_read_pos;
|
||||
w = rb_write_pos;
|
||||
|
||||
if (r == w) {
|
||||
space = 0;
|
||||
} else if (w < r) {
|
||||
space = rb_len - r + w;
|
||||
} else {
|
||||
space = w - r;
|
||||
}
|
||||
|
||||
return space;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ bool has_data() const {
|
||||
return rb && rb_read_pos != rb_write_pos;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ int16_t *get_write_buffer() { return read_buf; }
|
||||
_FORCE_INLINE_ float *get_write_buffer() { return read_buf; }
|
||||
_FORCE_INLINE_ void write(uint32_t p_frames) {
|
||||
|
||||
ERR_FAIL_COND(p_frames >= rb_len);
|
||||
|
@ -151,7 +167,8 @@ public:
|
|||
|
||||
Error setup(int p_channels, int p_src_mix_rate, int p_target_mix_rate, int p_buffer_msec, int p_minbuff_needed = -1);
|
||||
void clear();
|
||||
bool mix(int32_t *p_dest, int p_frames);
|
||||
bool mix(AudioFrame *p_dest, int p_frames);
|
||||
int get_num_of_ready_frames();
|
||||
|
||||
AudioRBResampler();
|
||||
~AudioRBResampler();
|
||||
|
|
|
@ -876,6 +876,8 @@ void AudioServer::init() {
|
|||
#ifdef TOOLS_ENABLED
|
||||
set_edited(false); //avoid editors from thinking this was edited
|
||||
#endif
|
||||
|
||||
GLOBAL_DEF("audio/video_delay_compensation_ms", 0);
|
||||
}
|
||||
|
||||
void AudioServer::load_default_bus_layout() {
|
||||
|
|
|
@ -122,6 +122,43 @@ bool OpusVorbisDecoder::getPCMS16(WebMFrame &frame, short *buffer, int &numOutSa
|
|||
return false;
|
||||
}
|
||||
|
||||
bool OpusVorbisDecoder::getPCMF(WebMFrame &frame, float *buffer, int &numOutSamples) {
|
||||
if (m_vorbis) {
|
||||
m_vorbis->op.packet = frame.buffer;
|
||||
m_vorbis->op.bytes = frame.bufferSize;
|
||||
|
||||
if (vorbis_synthesis(&m_vorbis->block, &m_vorbis->op))
|
||||
return false;
|
||||
if (vorbis_synthesis_blockin(&m_vorbis->dspState, &m_vorbis->block))
|
||||
return false;
|
||||
|
||||
const int maxSamples = getBufferSamples();
|
||||
int samplesCount, count = 0;
|
||||
float **pcm;
|
||||
while ((samplesCount = vorbis_synthesis_pcmout(&m_vorbis->dspState, &pcm))) {
|
||||
const int toConvert = samplesCount <= maxSamples ? samplesCount : maxSamples;
|
||||
for (int c = 0; c < m_channels; ++c) {
|
||||
float *samples = pcm[c];
|
||||
for (int i = 0, j = c; i < toConvert; ++i, j += m_channels) {
|
||||
buffer[count + j] = samples[i];
|
||||
}
|
||||
}
|
||||
vorbis_synthesis_read(&m_vorbis->dspState, toConvert);
|
||||
count += toConvert;
|
||||
}
|
||||
|
||||
numOutSamples = count;
|
||||
return true;
|
||||
} else if (m_opus) {
|
||||
const int samples = opus_decode_float(m_opus, frame.buffer, frame.bufferSize, buffer, m_numSamples, 0);
|
||||
if (samples >= 0) {
|
||||
numOutSamples = samples;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OpusVorbisDecoder::openVorbis(const WebMDemuxer &demuxer)
|
||||
{
|
||||
size_t extradataSize = 0;
|
||||
|
|
|
@ -44,7 +44,7 @@ public:
|
|||
{
|
||||
return m_numSamples;
|
||||
}
|
||||
|
||||
bool getPCMF(WebMFrame &frame, float *buffer, int &numOutSamples);
|
||||
bool getPCMS16(WebMFrame &frame, short *buffer, int &numOutSamples);
|
||||
|
||||
private:
|
||||
|
|
Loading…
Reference in New Issue