Fix video playback

This adds support to

- VideoPlayer
- VideoStreamWebm
- VideoStreamTheora
This commit is contained in:
Matt Hughes 2017-09-14 15:45:02 -05:00 committed by Hiroshi Ogawa
parent e8f8359b2e
commit 3edd3cd377
32 changed files with 873 additions and 331 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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