Răsfoiți Sursa

Fix video playback

This adds support to

- VideoPlayer
- VideoStreamWebm
- VideoStreamTheora
Matt Hughes 8 ani în urmă
părinte
comite
3edd3cd377

+ 1 - 2
modules/ogg/config.py

@@ -1,7 +1,6 @@
 
 def can_build(platform):
-#    return True
-    return False
+    return True
 
 
 def configure(env):

+ 9 - 2
modules/opus/SCsub

@@ -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 - 2
modules/opus/config.py

@@ -1,7 +1,6 @@
 
 def can_build(platform):
-#    return True
-    return False
+    return True
 
 
 def configure(env):

+ 36 - 0
modules/opus/stub/register_types.cpp

@@ -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() {}

+ 31 - 0
modules/opus/stub/register_types.h

@@ -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 - 2
modules/theora/config.py

@@ -1,7 +1,6 @@
 
 def can_build(platform):
-#    return True
-     return False
+    return True
 
 
 def configure(env):

+ 6 - 7
modules/theora/register_types.cpp

@@ -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);
 }

+ 89 - 0
modules/theora/resource_importer_theora.cpp

@@ -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() {
+}

+ 57 - 0
modules/theora/resource_importer_theora.h

@@ -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

+ 9 - 37
modules/theora/video_stream_theora.cpp

@@ -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;
-
-	VideoStreamTheora *stream = memnew(VideoStreamTheora);
-	stream->set_file(p_path);
-
-	if (r_error)
-		*r_error = OK;
-
-	return Ref<VideoStreamTheora>(stream);
-}
+void VideoStreamTheora::_bind_methods() {
 
-void ResourceFormatLoaderVideoStreamTheora::get_recognized_extensions(List<String> *p_extensions) const {
+	ClassDB::bind_method(D_METHOD("set_file", "file"), &VideoStreamTheora::set_file);
+	ClassDB::bind_method(D_METHOD("get_file"), &VideoStreamTheora::get_file);
 
-	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");
 }

+ 7 - 9
modules/theora/video_stream_theora.h

@@ -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

+ 8 - 2
modules/vorbis/SCsub

@@ -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")

+ 1 - 3
modules/vorbis/audio_stream_ogg_vorbis.cpp

@@ -106,8 +106,6 @@ int AudioStreamPlaybackOGGVorbis::mix(int16_t *p_bufer, 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_bufer, todo * stream_channels * sizeof(int16_t), 1, 2, 1, &current_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;
 }

+ 1 - 1
modules/vorbis/audio_stream_ogg_vorbis.h

@@ -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 - 2
modules/vorbis/config.py

@@ -1,7 +1,6 @@
 
 def can_build(platform):
-#    return True
-    return False
+    return True
 
 
 def configure(env):

+ 36 - 0
modules/vorbis/stub/register_types.cpp

@@ -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() {}

+ 31 - 0
modules/vorbis/stub/register_types.h

@@ -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 - 2
modules/webm/config.py

@@ -1,7 +1,6 @@
 
 def can_build(platform):
-#    return True
-    return False
+    return True
 
 
 def configure(env):

+ 2 - 2
modules/webm/libvpx/SCsub

@@ -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)

+ 6 - 7
modules/webm/register_types.cpp

@@ -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);
 }

+ 95 - 0
modules/webm/resource_importer_webm.cpp

@@ -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() {
+}

+ 55 - 0
modules/webm/resource_importer_webm.h

@@ -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

+ 73 - 80
modules/webm/video_stream_webm.cpp

@@ -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;

+ 9 - 14
modules/webm/video_stream_webm.h

@@ -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;
-};

+ 136 - 8
scene/gui/video_player.cpp

@@ -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) {
-
-	if (resampler.is_ready()) {
+bool VideoPlayer::mix(AudioFrame *p_buffer, int p_frames) {
+
+	// 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() {

+ 20 - 2
scene/gui/video_player.h

@@ -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;
+
+	StringName bus;
 
-	static int _audio_mix_callback(void *p_udata, const int16_t *p_data, int p_frames);
+	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();
 };

+ 2 - 2
scene/resources/video_stream.h

@@ -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;

+ 79 - 131
servers/audio/audio_rb_resampler.cpp

@@ -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];
-
-#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;
+			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];
+
+			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];
-
-#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;
+			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];
+
+			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];
-
-#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;
+			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];
+
+			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) {
-
-			//print_line("fadeout");
-			for (uint32_t c = 0; c < channels; c++) {
+		if (src_read > read_space)
+			src_read = read_space;
 
-				for (int i = 0; i < todo; i++) {
+		rb_read_pos = (rb_read_pos + src_read) & rb_mask;
 
-					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;

+ 33 - 16
servers/audio/audio_rb_resampler.h

@@ -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
-
-		int todo;
-		int read_pos_cache = rb_read_pos;
+	_FORCE_INLINE_ int get_writer_space() const {
+		int space, r, w;
 
-		if (read_pos_cache == rb_write_pos) {
-			todo = rb_len - 1;
-		} else if (read_pos_cache > rb_write_pos) {
+		r = rb_read_pos;
+		w = 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 {
+			space = (rb_len - r) + w - 1;
+		}
+
+		return space;
+	}
+
+	_FORCE_INLINE_ int get_reader_space() const {
+		int space, r, w;
 
-			todo = (rb_len - rb_write_pos) + read_pos_cache - 1;
+		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 todo;
+		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();

+ 2 - 0
servers/audio_server.cpp

@@ -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() {

+ 37 - 0
thirdparty/libsimplewebm/OpusVorbisDecoder.cpp

@@ -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;

+ 1 - 1
thirdparty/libsimplewebm/OpusVorbisDecoder.hpp

@@ -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: