From d710b265f8c9c94f3315d2d2ae2267c7437eb179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C5=82a=C5=BCej=20Szczygie=C5=82?= Date: Mon, 17 Oct 2016 17:14:07 +0200 Subject: [PATCH] Add WebM module Use already existing libraries: libvorbis and libopus. Also use newly added libraries: libvpx, libwebm, libsimplewebm. --- SConstruct | 1 + modules/theora/yuv2rgb.h | 6 +- modules/webm/SCsub | 34 +++ modules/webm/config.py | 6 + modules/webm/libvpx/SCsub | 390 ++++++++++++++++++++++++ modules/webm/libvpx/yasm_osx_fat.py | 38 +++ modules/webm/register_types.cpp | 45 +++ modules/webm/register_types.h | 30 ++ modules/webm/video_stream_webm.cpp | 446 ++++++++++++++++++++++++++++ modules/webm/video_stream_webm.h | 128 ++++++++ platform/x11/detect.py | 3 + 11 files changed, 1124 insertions(+), 3 deletions(-) create mode 100644 modules/webm/SCsub create mode 100644 modules/webm/config.py create mode 100644 modules/webm/libvpx/SCsub create mode 100644 modules/webm/libvpx/yasm_osx_fat.py create mode 100644 modules/webm/register_types.cpp create mode 100644 modules/webm/register_types.h create mode 100644 modules/webm/video_stream_webm.cpp create mode 100644 modules/webm/video_stream_webm.h diff --git a/SConstruct b/SConstruct index 14aba28818..32ef884c49 100644 --- a/SConstruct +++ b/SConstruct @@ -121,6 +121,7 @@ opts.Add('gdscript','Build GDSCript support: (yes/no)','yes') opts.Add('libogg','Ogg library for ogg container support (system/builtin)','builtin') opts.Add('libvorbis','Ogg Vorbis library for vorbis support (system/builtin)','builtin') opts.Add('libtheora','Theora library for theora module (system/builtin)','builtin') +opts.Add('libvpx','VPX library for webm module (system/builtin)','builtin') opts.Add('opus','Opus and opusfile library for Opus format support: (system/builtin)','builtin') opts.Add('minizip','Build Minizip Archive Support: (yes/no)','yes') opts.Add('squish','Squish library for BC Texture Compression in editor (system/builtin)','builtin') diff --git a/modules/theora/yuv2rgb.h b/modules/theora/yuv2rgb.h index 59101bd057..431b47e651 100644 --- a/modules/theora/yuv2rgb.h +++ b/modules/theora/yuv2rgb.h @@ -801,7 +801,7 @@ do { \ *(DSTPTR)++ = 255; \ } while (0 == 1) -void yuv422_2_rgb8888(uint8_t *dst_ptr, +static void yuv422_2_rgb8888(uint8_t *dst_ptr, const uint8_t *y_ptr, const uint8_t *u_ptr, const uint8_t *v_ptr, @@ -912,7 +912,7 @@ do { \ (DSTPTR) = 0xFF000000 | (Y & 0xFF) | (0xFF00 & (Y>>14)) | (0xFF0000 & (Y<<5));\ } while (0 == 1) -void yuv420_2_rgb8888(uint8_t *dst_ptr_, +static void yuv420_2_rgb8888(uint8_t *dst_ptr_, const uint8_t *y_ptr, const uint8_t *u_ptr, const uint8_t *v_ptr, @@ -1034,7 +1034,7 @@ do { \ *(DSTPTR)++ = 255; \ } while (0 == 1) -void yuv444_2_rgb8888(uint8_t *dst_ptr, +static void yuv444_2_rgb8888(uint8_t *dst_ptr, const uint8_t *y_ptr, const uint8_t *u_ptr, const uint8_t *v_ptr, diff --git a/modules/webm/SCsub b/modules/webm/SCsub new file mode 100644 index 0000000000..fbd936a2c3 --- /dev/null +++ b/modules/webm/SCsub @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_webm = env_modules.Clone() + +# Thirdparty source files +thirdparty_libsimplewebm_dir = "#thirdparty/libsimplewebm/" +thirdparty_libsimplewebm_sources = [ + "libwebm/mkvparser/mkvparser.cc", + "OpusVorbisDecoder.cpp", + "VPXDecoder.cpp", + "WebMDemuxer.cpp", +] +thirdparty_libsimplewebm_sources = [thirdparty_libsimplewebm_dir + file for file in thirdparty_libsimplewebm_sources] + +env_webm.add_source_files(env.modules_sources, thirdparty_libsimplewebm_sources) +env_webm.Append(CPPPATH = [thirdparty_libsimplewebm_dir, thirdparty_libsimplewebm_dir + "libwebm/"]) + +# also requires libogg, libvorbis and libopus +if (env["libogg"] != "system"): # builtin + env_webm.Append(CPPPATH = ["#thirdparty/libogg"]) +if (env["libvorbis"] != "system"): # builtin + env_webm.Append(CPPPATH = ["#thirdparty/libvorbis"]) +if (env["opus"] != "system"): # builtin + env_webm.Append(CPPPATH = ["#thirdparty"]) + +if (env["libvpx"] != "system"): # builtin + Export('env_webm') + SConscript("libvpx/SCsub") + +# Godot source files +env_webm.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/webm/config.py b/modules/webm/config.py new file mode 100644 index 0000000000..368e97e152 --- /dev/null +++ b/modules/webm/config.py @@ -0,0 +1,6 @@ + +def can_build(platform): + return True + +def configure(env): + pass diff --git a/modules/webm/libvpx/SCsub b/modules/webm/libvpx/SCsub new file mode 100644 index 0000000000..706db97206 --- /dev/null +++ b/modules/webm/libvpx/SCsub @@ -0,0 +1,390 @@ +#!/usr/bin/env python + +libvpx_dir = "#thirdparty/libvpx/" + +libvpx_sources = [ + "vp8/vp8_dx_iface.c", + + "vp8/common/generic/systemdependent.c", + + "vp8/common/alloccommon.c", + "vp8/common/blockd.c", + "vp8/common/copy_c.c", + "vp8/common/debugmodes.c", + "vp8/common/dequantize.c", + "vp8/common/entropy.c", + "vp8/common/entropymode.c", + "vp8/common/entropymv.c", + "vp8/common/extend.c", + "vp8/common/filter.c", + "vp8/common/findnearmv.c", + "vp8/common/idct_blk.c", + "vp8/common/idctllm.c", + "vp8/common/loopfilter_filters.c", + "vp8/common/mbpitch.c", + "vp8/common/modecont.c", + "vp8/common/quant_common.c", + "vp8/common/reconinter.c", + "vp8/common/reconintra.c", + "vp8/common/reconintra4x4.c", + "vp8/common/rtcd.c", + "vp8/common/setupintrarecon.c", + "vp8/common/swapyv12buffer.c", + "vp8/common/treecoder.c", + "vp8/common/vp8_loopfilter.c", + + "vp8/decoder/dboolhuff.c", + "vp8/decoder/decodeframe.c", + "vp8/decoder/decodemv.c", + "vp8/decoder/detokenize.c", + "vp8/decoder/onyxd_if.c", + "vp8/decoder/threading.c", + + + "vp9/vp9_dx_iface.c", + + "vp9/common/vp9_alloccommon.c", + "vp9/common/vp9_blockd.c", + "vp9/common/vp9_common_data.c", + "vp9/common/vp9_debugmodes.c", + "vp9/common/vp9_entropy.c", + "vp9/common/vp9_entropymode.c", + "vp9/common/vp9_entropymv.c", + "vp9/common/vp9_filter.c", + "vp9/common/vp9_frame_buffers.c", + "vp9/common/vp9_idct.c", + "vp9/common/vp9_loopfilter.c", + "vp9/common/vp9_mvref_common.c", + "vp9/common/vp9_pred_common.c", + "vp9/common/vp9_quant_common.c", + "vp9/common/vp9_reconinter.c", + "vp9/common/vp9_reconintra.c", + "vp9/common/vp9_rtcd.c", + "vp9/common/vp9_scale.c", + "vp9/common/vp9_scan.c", + "vp9/common/vp9_seg_common.c", + "vp9/common/vp9_thread_common.c", + "vp9/common/vp9_tile_common.c", + + "vp9/decoder/vp9_decodeframe.c", + "vp9/decoder/vp9_decodemv.c", + "vp9/decoder/vp9_decoder.c", + "vp9/decoder/vp9_detokenize.c", + "vp9/decoder/vp9_dsubexp.c", + "vp9/decoder/vp9_dthread.c", + + + "vpx/src/vpx_codec.c", + "vpx/src/vpx_decoder.c", + "vpx/src/vpx_image.c", + "vpx/src/vpx_psnr.c", + + + "vpx_dsp/bitreader.c", + "vpx_dsp/bitreader_buffer.c", + "vpx_dsp/intrapred.c", + "vpx_dsp/inv_txfm.c", + "vpx_dsp/loopfilter.c", + "vpx_dsp/prob.c", + "vpx_dsp/vpx_convolve.c", + "vpx_dsp/vpx_dsp_rtcd.c", + + + "vpx_mem/vpx_mem.c", + + + "vpx_scale/vpx_scale_rtcd.c", + + "vpx_scale/generic/yv12config.c", + "vpx_scale/generic/yv12extend.c", + + + "vpx_util/vpx_thread.c" +] + +libvpx_sources_intrin_x86 = [ + "vp8/common/x86/filter_x86.c", + "vp8/common/x86/loopfilter_x86.c", + "vp8/common/x86/vp8_asm_stubs.c", + + + "vpx_dsp/x86/vpx_asm_stubs.c" +] +libvpx_sources_intrin_x86_mmx = [ + "vp8/common/x86/idct_blk_mmx.c", +] +libvpx_sources_intrin_x86_sse2 = [ + "vp8/common/x86/idct_blk_sse2.c", + + + "vp9/common/x86/vp9_idct_intrin_sse2.c", + + + "vpx_dsp/x86/inv_txfm_sse2.c", + "vpx_dsp/x86/loopfilter_sse2.c", +] +libvpx_sources_intrin_x86_ssse3 = [ + "vpx_dsp/x86/vpx_subpixel_8t_intrin_ssse3.c" +] +libvpx_sources_intrin_x86_avx2 = [ + "vpx_dsp/x86/loopfilter_avx2.c", + "vpx_dsp/x86/vpx_subpixel_8t_intrin_avx2.c" +] +libvpx_sources_x86asm = [ + "vp8/common/x86/copy_sse2.asm", + "vp8/common/x86/copy_sse3.asm", + "vp8/common/x86/dequantize_mmx.asm", + "vp8/common/x86/idctllm_mmx.asm", + "vp8/common/x86/idctllm_sse2.asm", + "vp8/common/x86/iwalsh_mmx.asm", + "vp8/common/x86/iwalsh_sse2.asm", + "vp8/common/x86/loopfilter_sse2.asm", + "vp8/common/x86/recon_mmx.asm", + "vp8/common/x86/recon_sse2.asm", + "vp8/common/x86/subpixel_mmx.asm", + "vp8/common/x86/subpixel_sse2.asm", + "vp8/common/x86/subpixel_ssse3.asm", + "vp8/common/x86/vp8_loopfilter_mmx.asm", + + + "vpx_dsp/x86/intrapred_sse2.asm", + "vpx_dsp/x86/intrapred_ssse3.asm", + "vpx_dsp/x86/inv_wht_sse2.asm", + "vpx_dsp/x86/vpx_convolve_copy_sse2.asm", + "vpx_dsp/x86/vpx_subpixel_8t_sse2.asm", + "vpx_dsp/x86/vpx_subpixel_8t_ssse3.asm", + "vpx_dsp/x86/vpx_subpixel_bilinear_sse2.asm", + "vpx_dsp/x86/vpx_subpixel_bilinear_ssse3.asm", + + + "vpx_ports/emms.asm" +] +libvpx_sources_x86_64asm = [ + "vp8/common/x86/loopfilter_block_sse2_x86_64.asm", + + + "vpx_dsp/x86/inv_txfm_ssse3_x86_64.asm" +] + +libvpx_sources_arm = [ + "vpx_ports/arm_cpudetect.c", + + + "vp8/common/arm/loopfilter_arm.c", +] +libvpx_sources_arm_neon = [ + "vp8/common/arm/neon/bilinearpredict_neon.c", + "vp8/common/arm/neon/copymem_neon.c", + "vp8/common/arm/neon/dc_only_idct_add_neon.c", + "vp8/common/arm/neon/dequant_idct_neon.c", + "vp8/common/arm/neon/dequantizeb_neon.c", + "vp8/common/arm/neon/idct_blk_neon.c", + "vp8/common/arm/neon/idct_dequant_0_2x_neon.c", + "vp8/common/arm/neon/idct_dequant_full_2x_neon.c", + "vp8/common/arm/neon/iwalsh_neon.c", + "vp8/common/arm/neon/loopfiltersimplehorizontaledge_neon.c", + "vp8/common/arm/neon/loopfiltersimpleverticaledge_neon.c", + "vp8/common/arm/neon/mbloopfilter_neon.c", + "vp8/common/arm/neon/shortidct4x4llm_neon.c", + "vp8/common/arm/neon/sixtappredict_neon.c", + "vp8/common/arm/neon/vp8_loopfilter_neon.c", + + + "vp9/common/arm/neon/vp9_iht4x4_add_neon.c", + "vp9/common/arm/neon/vp9_iht8x8_add_neon.c", + + + "vpx_dsp/arm/idct16x16_1_add_neon.c", + "vpx_dsp/arm/idct16x16_add_neon.c", + "vpx_dsp/arm/idct16x16_neon.c", + "vpx_dsp/arm/idct32x32_1_add_neon.c", + "vpx_dsp/arm/idct32x32_add_neon.c", + "vpx_dsp/arm/idct4x4_1_add_neon.c", + "vpx_dsp/arm/idct4x4_add_neon.c", + "vpx_dsp/arm/idct8x8_1_add_neon.c", + "vpx_dsp/arm/idct8x8_add_neon.c", + "vpx_dsp/arm/intrapred_neon.c", + "vpx_dsp/arm/loopfilter_16_neon.c", + "vpx_dsp/arm/loopfilter_4_neon.c", + "vpx_dsp/arm/loopfilter_8_neon.c", + "vpx_dsp/arm/loopfilter_neon.c", + "vpx_dsp/arm/vpx_convolve8_avg_neon.c", + "vpx_dsp/arm/vpx_convolve8_neon.c", + "vpx_dsp/arm/vpx_convolve_avg_neon.c", + "vpx_dsp/arm/vpx_convolve_copy_neon.c", + "vpx_dsp/arm/vpx_convolve_neon.c" +] +libvpx_sources_arm_neon_gas = [ + "vpx_dsp/arm/gas/intrapred_neon_asm.s", + "vpx_dsp/arm/gas/loopfilter_mb_neon.s", + "vpx_dsp/arm/gas/save_reg_neon.s" +] +libvpx_sources_arm_neon_armasm_ms = [ + "vpx_dsp/arm/armasm_ms/intrapred_neon_asm.asm", + "vpx_dsp/arm/armasm_ms/loopfilter_mb_neon.asm", + "vpx_dsp/arm/armasm_ms/save_reg_neon.asm" +] +libvpx_sources_arm_neon_gas_apple = [ + "vpx_dsp/arm/gas_apple/intrapred_neon_asm.s", + "vpx_dsp/arm/gas_apple/loopfilter_mb_neon.s", + "vpx_dsp/arm/gas_apple/save_reg_neon.s" +] + +libvpx_sources = [libvpx_dir + file for file in libvpx_sources] +libvpx_sources_intrin_x86 = [libvpx_dir + file for file in libvpx_sources_intrin_x86] +libvpx_sources_intrin_x86_mmx = [libvpx_dir + file for file in libvpx_sources_intrin_x86_mmx] +libvpx_sources_intrin_x86_sse2 = [libvpx_dir + file for file in libvpx_sources_intrin_x86_sse2] +libvpx_sources_intrin_x86_ssse3 = [libvpx_dir + file for file in libvpx_sources_intrin_x86_ssse3] +libvpx_sources_intrin_x86_avx2 = [libvpx_dir + file for file in libvpx_sources_intrin_x86_avx2] +libvpx_sources_x86asm = [libvpx_dir + file for file in libvpx_sources_x86asm] +libvpx_sources_x86_64asm = [libvpx_dir + file for file in libvpx_sources_x86_64asm] +libvpx_sources_arm = [libvpx_dir + file for file in libvpx_sources_arm] +libvpx_sources_arm_neon = [libvpx_dir + file for file in libvpx_sources_arm_neon] +libvpx_sources_arm_neon_gas = [libvpx_dir + file for file in libvpx_sources_arm_neon_gas] +libvpx_sources_arm_neon_armasm_ms = [libvpx_dir + file for file in libvpx_sources_arm_neon_armasm_ms] +libvpx_sources_arm_neon_gas_apple = [libvpx_dir + file for file in libvpx_sources_arm_neon_gas_apple] + + +Import('env') +Import('env_webm') + +env_webm.Append(CPPPATH = [libvpx_dir]) + +env_libvpx = env.Clone() +env_libvpx.Append(CPPPATH = [libvpx_dir]) + +cpu_bits = env["bits"] +osx_fat = (env["platform"] == 'osx' and cpu_bits == 'fat') +webm_cpu_x86 = False +webm_cpu_arm = False +if env["platform"] == 'winrt': + if 'arm' in env["PROGSUFFIX"]: + webm_cpu_arm = True + else: + webm_cpu_x86 = True +else: + is_android_x86 = (env["platform"] == 'android' and env["android_arch"] == 'x86') + if is_android_x86: + cpu_bits = '32' + if osx_fat: + webm_cpu_x86 = True + else: + webm_cpu_x86 = (cpu_bits == '32' or cpu_bits == '64') and (env["platform"] == 'windows' or env["platform"] == 'x11' or env["platform"] == 'osx' or env["platform"] == 'haiku' or is_android_x86) + webm_cpu_arm = env["platform"] == 'iphone' or env["platform"] == 'bb10' or (env["platform"] == 'android' and env["android_arch"] != 'x86') + +if webm_cpu_x86: + import subprocess + import os + + yasm_paths = [ + "yasm", + "../../../yasm", + ] + + yasm_found = False + + devnull = open(os.devnull) + for yasm_path in yasm_paths: + try: + yasm_found = True + subprocess.Popen([yasm_path, "--version"], stdout=devnull, stderr=devnull).communicate() + except: + yasm_found = False + if yasm_found: + break + + if not yasm_found: + webm_cpu_x86 = False + print "YASM is necessary for WebM SIMD optimizations." + +webm_simd_optimizations = False + +if webm_cpu_x86: + if osx_fat: + #'osx' platform only: run python script which will compile using 'yasm' command and then merge 32-bit and 64-bit using 'lipo' command + env_libvpx["AS"] = 'python modules/webm/libvpx/yasm_osx_fat.py' + env_libvpx["ASFLAGS"] = '-I' + libvpx_dir[1:] + env_libvpx["ASCOM"] = '$AS $ASFLAGS $TARGET $SOURCES' + else: + if env["platform"] == 'windows' or env["platform"] == 'winrt': + env_libvpx["ASFORMAT"] = 'win' + elif env["platform"] == 'osx': + env_libvpx["ASFORMAT"] = 'macho' + else: + env_libvpx["ASFORMAT"] = 'elf' + env_libvpx["ASFORMAT"] += cpu_bits + + env_libvpx["AS"] = 'yasm' + env_libvpx["ASFLAGS"] = '-I' + libvpx_dir[1:] + ' -f $ASFORMAT -D $ASCPU' + env_libvpx["ASCOM"] = '$AS $ASFLAGS -o $TARGET $SOURCES' + + if cpu_bits == '32': + env_libvpx["ASCPU"] = 'X86_32' + elif cpu_bits == '64': + env_libvpx["ASCPU"] = 'X86_64' + + env_libvpx.Append(CCFLAGS=['-DWEBM_X86ASM']) + + webm_simd_optimizations = True + +if webm_cpu_arm: + if env["platform"] == 'iphone': + env_libvpx["ASFLAGS"] = '-arch armv7' + elif env["platform"] == 'android': + env_libvpx["ASFLAGS"] = '-mfpu=neon' + elif env["platform"] == 'winrt': + env_libvpx["AS"] = 'armasm' + env_libvpx["ASFLAGS"] = '' + env_libvpx["ASCOM"] = '$AS $ASFLAGS -o $TARGET $SOURCES' + + env_libvpx.Append(CCFLAGS=['-DWEBM_ARMASM']) + + 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!" + + +env_libvpx.add_source_files(env.modules_sources, libvpx_sources) +if webm_cpu_x86: + is_clang_or_gcc = ('gcc' in env["CC"]) or ('clang' in env["CC"]) + + env_libvpx_mmx = env_libvpx.Clone() + if cpu_bits == '32' and is_clang_or_gcc: + env_libvpx_mmx.Append(CCFLAGS=['-mmmx']) + env_libvpx_mmx.add_source_files(env.modules_sources, libvpx_sources_intrin_x86_mmx) + + env_libvpx_sse2 = env_libvpx.Clone() + if cpu_bits == '32' and is_clang_or_gcc: + env_libvpx_sse2.Append(CCFLAGS=['-msse2']) + env_libvpx_sse2.add_source_files(env.modules_sources, libvpx_sources_intrin_x86_sse2) + + env_libvpx_ssse3 = env_libvpx.Clone() + if is_clang_or_gcc: + env_libvpx_ssse3.Append(CCFLAGS=['-mssse3']) + env_libvpx_ssse3.add_source_files(env.modules_sources, libvpx_sources_intrin_x86_ssse3) + + env_libvpx_avx2 = env_libvpx.Clone() + if is_clang_or_gcc: + env_libvpx_avx2.Append(CCFLAGS=['-mavx2']) + env_libvpx_avx2.add_source_files(env.modules_sources, libvpx_sources_intrin_x86_avx2) + + env_libvpx.add_source_files(env.modules_sources, libvpx_sources_intrin_x86) + + env_libvpx.add_source_files(env.modules_sources, libvpx_sources_x86asm) + if cpu_bits == '64' or osx_fat: + env_libvpx.add_source_files(env.modules_sources, libvpx_sources_x86_64asm) +elif webm_cpu_arm: + env_libvpx.add_source_files(env.modules_sources, libvpx_sources_arm) + + env_libvpx_neon = env_libvpx.Clone() + if env["platform"] == 'android' and env["android_arch"] == 'armv6': + env_libvpx_neon.Append(CCFLAGS=['-mfpu=neon']) + env_libvpx_neon.add_source_files(env.modules_sources, libvpx_sources_arm_neon) + + if env["platform"] == 'winrt': + env_libvpx.add_source_files(env.modules_sources, libvpx_sources_arm_neon_armasm_ms) + elif env["platform"] == 'iphone': + env_libvpx.add_source_files(env.modules_sources, libvpx_sources_arm_neon_gas_apple) + else: + env_libvpx.add_source_files(env.modules_sources, libvpx_sources_arm_neon_gas) diff --git a/modules/webm/libvpx/yasm_osx_fat.py b/modules/webm/libvpx/yasm_osx_fat.py new file mode 100644 index 0000000000..bcee50b389 --- /dev/null +++ b/modules/webm/libvpx/yasm_osx_fat.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +import sys +import os + +includes = sys.argv[1] +output_file = sys.argv[2] +input_file = sys.argv[3] + +can_remove = {} + +lipo_command = '' + +exit_code = 1 + +for arch in ['32', '64']: + if arch == '32' and input_file.endswith('x86_64.asm'): + can_remove[arch] = False + else: + command = 'yasm ' + includes + ' -f macho' + arch + ' -D X86_' + arch + ' -o ' + output_file + '.' + arch + ' ' + input_file + print(command) + if os.system(command) == 0: + lipo_command += output_file + '.' + arch + ' ' + can_remove[arch] = True + else: + can_remove[arch] = False + +if lipo_command != '': + lipo_command = 'lipo -create ' + lipo_command + '-output ' + output_file + print(lipo_command) + if os.system(lipo_command) == 0: + exit_code = 0 + +for arch in ['32', '64']: + if can_remove[arch]: + os.remove(output_file + '.' + arch) + +sys.exit(exit_code) diff --git a/modules/webm/register_types.cpp b/modules/webm/register_types.cpp new file mode 100644 index 0000000000..07031caad1 --- /dev/null +++ b/modules/webm/register_types.cpp @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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" + +#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); + ObjectTypeDB::register_type(); +} + +void unregister_webm_types() { + + memdelete(webm_stream_loader); +} diff --git a/modules/webm/register_types.h b/modules/webm/register_types.h new file mode 100644 index 0000000000..62f08646a4 --- /dev/null +++ b/modules/webm/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_webm_types(); +void unregister_webm_types(); diff --git a/modules/webm/video_stream_webm.cpp b/modules/webm/video_stream_webm.cpp new file mode 100644 index 0000000000..dc2266804d --- /dev/null +++ b/modules/webm/video_stream_webm.cpp @@ -0,0 +1,446 @@ +/*************************************************************************/ +/* av_stream_webm.cpp.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "video_stream_webm.h" + +#include "VPXDecoder.hpp" +#include "OpusVorbisDecoder.hpp" + +#include "mkvparser/mkvparser.h" +#include "../theora/yuv2rgb.h" + +#include "os/file_access.h" +#include "globals.h" + +#include + +class MkvReader : public mkvparser::IMkvReader { + +public: + MkvReader(const String &p_file) { + + file = FileAccess::open(p_file, FileAccess::READ); + ERR_FAIL_COND(!file); + } + ~MkvReader() { + + if (file) + memdelete(file); + } + + virtual int Read(long long pos, long len, unsigned char *buf) { + + if (file) { + + if (file->get_pos() != (size_t)pos) + file->seek(pos); + if (file->get_buffer(buf, len) == len) + return 0; + } + return -1; + } + + virtual int Length(long long *total, long long *available) { + + if (file) { + + const size_t len = file->get_len(); + if (total) + *total = len; + if (available) + *available = len; + return 0; + } + return -1; + } + +private: + FileAccess *file; +}; + +/**/ + +VideoStreamPlaybackWebm::VideoStreamPlaybackWebm() : + audio_track(0), + webm(NULL), + video(NULL), + audio(NULL), + video_frames(NULL), audio_frame(NULL), + video_frames_pos(0), video_frames_capacity(0), + num_decoded_samples(0), samples_offset(-1), + mix_callback(NULL), + mix_udata(NULL), + playing(false), paused(false), + delay_compensation(0.0), + time(0.0), video_frame_delay(0.0), video_pos(0.0), + texture(memnew(ImageTexture)), + pcm(NULL) +{} +VideoStreamPlaybackWebm::~VideoStreamPlaybackWebm() { + + delete_pointers(); +} + +bool VideoStreamPlaybackWebm::open_file(const String &p_file) { + + file_name = p_file; + webm = memnew(WebMDemuxer(new MkvReader(file_name), 0, audio_track)); + if (webm->isOpen()) { + + video = memnew(VPXDecoder(*webm, 8)); //TODO: Detect CPU threads + 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()); + } else { + + memdelete(audio); + audio = NULL; + } + + frame_data.resize((webm->getWidth() * webm->getHeight()) << 2); + texture->create(webm->getWidth(), webm->getHeight(), Image::FORMAT_RGBA, Texture::FLAG_FILTER | Texture::FLAG_VIDEO_SURFACE); + + return true; + } + memdelete(video); + video = NULL; + } + memdelete(webm); + webm = NULL; + return false; +} + +void VideoStreamPlaybackWebm::stop() { + + if (playing) { + + delete_pointers(); + + pcm = NULL; + + audio_frame = NULL; + video_frames = NULL; + + video = NULL; + audio = NULL; + + open_file(file_name); //Should not fail here... + + video_frames_capacity = video_frames_pos = 0; + num_decoded_samples = 0; + samples_offset = -1; + video_frame_delay = video_pos = 0.0; + } + time = 0.0; + playing = false; +} +void VideoStreamPlaybackWebm::play() { + + stop(); + + delay_compensation = Globals::get_singleton()->get("audio/video_delay_compensation_ms"); + delay_compensation /= 1000.0; + + playing = true; +} + +bool VideoStreamPlaybackWebm::is_playing() const { + + return playing; +} + +void VideoStreamPlaybackWebm::set_paused(bool p_paused) { + + paused = p_paused; +} +bool VideoStreamPlaybackWebm::is_paused(bool p_paused) const { + + return paused; +} + +void VideoStreamPlaybackWebm::set_loop(bool p_enable) { + + //Empty +} +bool VideoStreamPlaybackWebm::has_loop() const { + + return false; +} + +float VideoStreamPlaybackWebm::get_length() const { + + if (webm) + return webm->getLength(); + return 0.0f; +} + +float VideoStreamPlaybackWebm::get_pos() const { + + return video_pos; +} +void VideoStreamPlaybackWebm::seek_pos(float p_time) { + + //Not implemented +} + +void VideoStreamPlaybackWebm::set_audio_track(int p_idx) { + + audio_track = p_idx; +} + +Ref VideoStreamPlaybackWebm::get_texture() { + + return texture; +} +void VideoStreamPlaybackWebm::update(float p_delta) { + + if ((!playing || paused) || !video) + return; + + bool audio_buffer_full = false; + + if (samples_offset > -1) { + + //Mix remaining samples + const int to_read = num_decoded_samples - samples_offset; + const int mixed = mix_callback(mix_udata, pcm + samples_offset * webm->getChannels(), to_read); + if (mixed != to_read) { + + samples_offset += mixed; + audio_buffer_full = true; + } else { + + samples_offset = -1; + } + } + + const bool hasAudio = (audio && mix_callback); + 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) { + + const int mixed = mix_callback(mix_udata, pcm, num_decoded_samples); + if (mixed != num_decoded_samples) { + + samples_offset = mixed; + audio_buffer_full = true; + } + } + + WebMFrame *video_frame; + if (video_frames_pos >= video_frames_capacity) { + + WebMFrame **video_frames_new = (WebMFrame **)memrealloc(video_frames, ++video_frames_capacity * sizeof(void *)); + ERR_FAIL_COND(!video_frames_new); //Out of memory + (video_frames = video_frames_new)[video_frames_capacity - 1] = memnew(WebMFrame); + } + video_frame = video_frames[video_frames_pos]; + + if (!webm->readFrame(video_frame, audio_frame)) //This will invalidate frames + break; //Can't demux, EOS? + + if (video_frame->isValid()) + ++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) { + + WebMFrame *video_frame = video_frames[0]; + if (video_frame->time <= time + video_delay) { + + if (video->decode(*video_frame)) { + + VPXDecoder::IMAGE_ERROR err; + VPXDecoder::Image image; + + while ((err = video->getImage(image)) != VPXDecoder::NO_FRAME) { + + want_this_frame = (time - video_frame->time <= video_frame_delay); + + if (want_this_frame) { + + if (err == VPXDecoder::NO_ERROR && image.w == webm->getWidth() && image.h == webm->getHeight()) { + + DVector::Write w = frame_data.write(); + bool converted = false; + + 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) { + + 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) { + +// 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_RGBA, frame_data)); //Zero copy send to visual server + } + + break; + } + } + } + + 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; + + if (video_frames_pos == 0 && webm->isEOS()) + stop(); +} + +void VideoStreamPlaybackWebm::set_mix_callback(VideoStreamPlayback::AudioMixCallback p_callback, void *p_userdata) { + + mix_callback = p_callback; + mix_udata = p_userdata; +} +int VideoStreamPlaybackWebm::get_channels() const { + + if (audio) + return webm->getChannels(); + return 0; +} +int VideoStreamPlaybackWebm::get_mix_rate() const { + + if (audio) + return webm->getSampleRate(); + return 0; +} + +inline bool VideoStreamPlaybackWebm::has_enough_video_frames() const +{ + if (video_frames_pos > 0) { + + const double audio_delay = AudioServer::get_singleton()->get_output_delay(); + const double video_time = video_frames[video_frames_pos - 1]->time; + return video_time >= time + audio_delay + delay_compensation; + } + return false; +} + +void VideoStreamPlaybackWebm::delete_pointers() { + + if (pcm) + memfree(pcm); + + if (audio_frame) + memdelete(audio_frame); + for (int i = 0; i < video_frames_capacity; ++i) + memdelete(video_frames[i]); + if (video_frames) + memfree(video_frames); + + if (video) + memdelete(video); + if (audio) + memdelete(audio); + + if (webm) + memdelete(webm); +} + +/**/ + +RES ResourceFormatLoaderVideoStreamWebm::load(const String &p_path, const String &p_original_path, Error *r_error) { + + Ref stream = memnew(VideoStreamWebm); + stream->set_file(p_path); + if (r_error) + *r_error = OK; + return stream; +} + +void ResourceFormatLoaderVideoStreamWebm::get_recognized_extensions(List *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.extension().to_lower(); + if (exl == "webm") + return "VideoStreamWebm"; + return ""; +} + +/**/ + +VideoStreamWebm::VideoStreamWebm() : + audio_track(0) +{} + +Ref VideoStreamWebm::instance_playback() { + + Ref pb = memnew(VideoStreamPlaybackWebm); + pb->set_audio_track(audio_track); + if (pb->open_file(file)) + return pb; + return NULL; +} + +void VideoStreamWebm::set_file(const String &p_file) { + + file = p_file; +} +void VideoStreamWebm::set_audio_track(int p_track) { + + audio_track = p_track; +} diff --git a/modules/webm/video_stream_webm.h b/modules/webm/video_stream_webm.h new file mode 100644 index 0000000000..d1e02d0663 --- /dev/null +++ b/modules/webm/video_stream_webm.h @@ -0,0 +1,128 @@ +/*************************************************************************/ +/* av_stream_webm.cpp.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "scene/resources/video_stream.h" +#include "io/resource_loader.h" + +class WebMFrame; +class WebMDemuxer; +class VPXDecoder; +class OpusVorbisDecoder; + +class VideoStreamPlaybackWebm : public VideoStreamPlayback { + + OBJ_TYPE(VideoStreamPlaybackWebm, VideoStreamPlayback) + + String file_name; + int audio_track; + + WebMDemuxer *webm; + VPXDecoder *video; + OpusVorbisDecoder *audio; + + WebMFrame **video_frames, *audio_frame; + int video_frames_pos, video_frames_capacity; + + int num_decoded_samples, samples_offset; + AudioMixCallback mix_callback; + void *mix_udata; + + bool playing, paused; + double delay_compensation; + double time, video_frame_delay, video_pos; + + DVector frame_data; + Ref texture; + + int16_t *pcm; + +public: + VideoStreamPlaybackWebm(); + ~VideoStreamPlaybackWebm(); + + bool open_file(const String &p_file); + + virtual void stop(); + virtual void play(); + + virtual bool is_playing() const; + + virtual void set_paused(bool p_paused); + virtual bool is_paused(bool p_paused) const; + + virtual void set_loop(bool p_enable); + virtual bool has_loop() const; + + virtual float get_length() const; + + virtual float get_pos() const; + virtual void seek_pos(float p_time); + + virtual void set_audio_track(int p_idx); + + virtual Ref get_texture(); + virtual void update(float p_delta); + + virtual void set_mix_callback(AudioMixCallback p_callback, void *p_userdata); + virtual int get_channels() const; + virtual int get_mix_rate() const; + +private: + inline bool has_enough_video_frames() const; + + void delete_pointers(); +}; + +/**/ + +class VideoStreamWebm : public VideoStream { + + OBJ_TYPE(VideoStreamWebm, VideoStream) + + String file; + int audio_track; + +public: + VideoStreamWebm(); + + virtual Ref instance_playback(); + + virtual void set_file(const String &p_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 *p_extensions) const; + virtual bool handles_type(const String &p_type) const; + virtual String get_resource_type(const String &p_path) const; +}; diff --git a/platform/x11/detect.py b/platform/x11/detect.py index eb71ac7409..554a461d1b 100644 --- a/platform/x11/detect.py +++ b/platform/x11/detect.py @@ -164,6 +164,9 @@ def configure(env): env["libvorbis"] = "system" # Needed to link against system libtheora env.ParseConfig('pkg-config theora theoradec --cflags --libs') + if (env["libvpx"] == "system"): + env.ParseConfig('pkg-config vpx --cflags --libs') + if (env["libvorbis"] == "system"): env["libogg"] = "system" # Needed to link against system libvorbis env.ParseConfig('pkg-config vorbis vorbisfile --cflags --libs')