1
0
Fork 0

CI-Build 2022-08-16

This commit is contained in:
arch1t3cht 2022-08-16 21:08:20 +02:00
commit 860a090de8
51 changed files with 2271 additions and 764 deletions

View File

@ -35,6 +35,8 @@ jobs:
-Dfribidi:docs=false
-Dlibass:fontconfig=disabled
-Davisynth=enabled
-Dbestsource=enabled
-Dvapoursynth=enabled
#- {
# name: Windows MinGW,
# os: windows-latest,
@ -56,7 +58,7 @@ jobs:
name: macOS Release,
os: macos-latest,
buildtype: release,
args: -Ddefault_library=static -Dbuild_osx_bundle=true -Dlocal_boost=true
args: -Ddefault_library=static -Dbuild_osx_bundle=true -Dlocal_boost=true -Davisynth=enabled -Dbestsource=enabled -Dvapoursynth=enabled
}
steps:

3
.gitignore vendored
View File

@ -19,6 +19,7 @@ tools/repack-thes-dict.dSYM
# Meson
build*/
subprojects/avisynth
subprojects/bestsource/
subprojects/boost*/
subprojects/cairo*
subprojects/ffmpeg
@ -30,6 +31,7 @@ subprojects/glib*
subprojects/googletest-*
subprojects/harfbuzz
subprojects/icu
subprojects/jansson
subprojects/libass
subprojects/libffi*
subprojects/libpng-*
@ -42,3 +44,4 @@ subprojects/zlib-*
subprojects/dirent-*
subprojects/hunspell-*
subprojects/uchardet-*
subprojects/vapoursynth

View File

@ -1,541 +0,0 @@
cmake_minimum_required(VERSION 3.14)
cmake_policy(SET CMP0074 NEW)
project(Aegisub)
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
include_directories("build")
include_directories("libaegisub/include")
include_directories("vendor/luajit/include")
add_library(libaegisub STATIC
libaegisub/common/parser.cpp
libaegisub/ass/dialogue_parser.cpp
libaegisub/ass/time.cpp
libaegisub/ass/uuencode.cpp
libaegisub/audio/provider.cpp
libaegisub/audio/provider_convert.cpp
libaegisub/audio/provider_dummy.cpp
libaegisub/audio/provider_hd.cpp
libaegisub/audio/provider_lock.cpp
libaegisub/audio/provider_pcm.cpp
libaegisub/audio/provider_ram.cpp
libaegisub/common/cajun/elements.cpp
libaegisub/common/cajun/reader.cpp
libaegisub/common/cajun/writer.cpp
libaegisub/lua/modules/lfs.cpp
libaegisub/lua/modules/re.cpp
libaegisub/lua/modules/unicode.cpp
libaegisub/lua/modules/lpeg.c
libaegisub/lua/modules.cpp
libaegisub/lua/script_reader.cpp
libaegisub/lua/utils.cpp
libaegisub/common/calltip_provider.cpp
libaegisub/common/character_count.cpp
libaegisub/common/charset.cpp
libaegisub/common/charset_6937.cpp
libaegisub/common/charset_conv.cpp
libaegisub/common/color.cpp
libaegisub/common/file_mapping.cpp
libaegisub/common/format.cpp
libaegisub/common/fs.cpp
libaegisub/common/hotkey.cpp
libaegisub/common/io.cpp
libaegisub/common/json.cpp
libaegisub/common/kana_table.cpp
libaegisub/common/karaoke_matcher.cpp
libaegisub/common/keyframe.cpp
libaegisub/common/line_iterator.cpp
libaegisub/common/log.cpp
libaegisub/common/mru.cpp
libaegisub/common/option.cpp
libaegisub/common/option_value.cpp
libaegisub/common/path.cpp
libaegisub/common/thesaurus.cpp
libaegisub/common/util.cpp
libaegisub/common/vfr.cpp
libaegisub/common/ycbcr_conv.cpp
libaegisub/common/dispatch.cpp
)
if (UNIX)
target_sources(libaegisub PRIVATE
libaegisub/unix/access.cpp
libaegisub/unix/fs.cpp
libaegisub/unix/log.cpp
libaegisub/unix/path.cpp
libaegisub/unix/util.cpp
)
elseif(WIN32)
target_sources(libaegisub PRIVATE
libaegisub/windows/access.cpp
libaegisub/windows/charset_conv_win.cpp
libaegisub/windows/fs.cpp
libaegisub/windows/lagi_pre.cpp
libaegisub/windows/log_win.cpp
libaegisub/windows/path_win.cpp
libaegisub/windows/util_win.cpp
)
endif(UNIX)
SET_TARGET_PROPERTIES(libaegisub PROPERTIES PREFIX "")
add_library(luabins STATIC
vendor/luabins/src/fwrite.c
vendor/luabins/src/load.c
vendor/luabins/src/luabins.c
vendor/luabins/src/luainternals.c
vendor/luabins/src/save.c
vendor/luabins/src/savebuffer.c
vendor/luabins/src/write.c
)
add_executable(luajit-minilua vendor/luajit/src/host/minilua.c)
if (NOT MSVC)
target_link_libraries(luajit-minilua m)
endif(NOT MSVC)
add_custom_command(TARGET luajit-minilua POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_SOURCE_DIR}/vendor/luajit/src/gen
COMMAND luajit-minilua ../dynasm/dynasm.lua -D P64 -D JIT -D FFI -D FPU -D HFABI -D VER= -o gen/buildvm_arch.h vm_x86.dasc
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src
)
add_custom_command(TARGET luajit-minilua POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/src/libresrc/default_config_win.json ${PROJECT_SOURCE_DIR}/src/libresrc/default_config_platform.json
COMMAND luajit-minilua ../../tools/respack.lua manifest.respack default_config.cpp default_config.h
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/libresrc
BYPRODUCTS ${PROJECT_SOURCE_DIR}/src/libresrc/default_config.cpp ${PROJECT_SOURCE_DIR}/src/libresrc/default_config.h
)
add_custom_command(TARGET luajit-minilua POST_BUILD
COMMAND luajit-minilua ../../tools/respack.lua manifest.respack ../libresrc/bitmap.cpp ../libresrc/bitmap.h
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/bitmaps
BYPRODUCTS ${PROJECT_SOURCE_DIR}/src/libresrc/bitmap.cpp ${PROJECT_SOURCE_DIR}/src/libresrc/bitmap.h
)
add_executable(luajit-buildvm
vendor/luajit/src/host/buildvm.c
vendor/luajit/src/host/buildvm_asm.c
vendor/luajit/src/host/buildvm_peobj.c
vendor/luajit/src/host/buildvm_lib.c
vendor/luajit/src/host/buildvm_fold.c
)
target_include_directories(luajit-buildvm PRIVATE vendor/luajit/src vendor/luajit/src/gen)
add_dependencies(luajit-buildvm luajit-minilua)
if(UNIX)
add_custom_command(TARGET luajit-buildvm POST_BUILD
COMMAND luajit-buildvm -m elfasm -o lj_vm.s
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src
BYPRODUCTS ${PROJECT_SOURCE_DIR}/vendor/luajit/src/lj_vm.s
)
set_property(SOURCE vendor/luajit/src/lj_vm.s PROPERTY LANGUAGE C)
elseif(MSVC)
add_custom_command(TARGET luajit-buildvm POST_BUILD
COMMAND luajit-buildvm -m peobj -o lj_vm.obj
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src
BYPRODUCTS ${PROJECT_SOURCE_DIR}/vendor/luajit/src/lj_vm.obj
)
endif(UNIX)
add_custom_command(TARGET luajit-buildvm POST_BUILD
COMMAND luajit-buildvm -m ffdef -o gen/lj_ffdef.h lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c
COMMAND luajit-buildvm -m bcdef -o gen/lj_bcdef.h lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c
COMMAND luajit-buildvm -m folddef -o gen/lj_folddef.h lj_opt_fold.c
COMMAND luajit-buildvm -m recdef -o gen/lj_recdef.h lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c
COMMAND luajit-buildvm -m libdef -o gen/lj_libdef.h lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c
COMMAND luajit-buildvm -m vmdef -o jit/vmdef.lua lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src
)
add_library(luajit STATIC
vendor/luajit/src/lj_gc.c
vendor/luajit/src/lj_err.c
vendor/luajit/src/lj_char.c
vendor/luajit/src/lj_bc.c
vendor/luajit/src/lj_obj.c
vendor/luajit/src/lj_str.c
vendor/luajit/src/lj_tab.c
vendor/luajit/src/lj_func.c
vendor/luajit/src/lj_udata.c
vendor/luajit/src/lj_meta.c
vendor/luajit/src/lj_debug.c
vendor/luajit/src/lj_state.c
vendor/luajit/src/lj_dispatch.c
vendor/luajit/src/lj_vmevent.c
vendor/luajit/src/lj_vmmath.c
vendor/luajit/src/lj_strscan.c
vendor/luajit/src/lj_api.c
vendor/luajit/src/lj_lex.c
vendor/luajit/src/lj_parse.c
vendor/luajit/src/lj_bcread.c
vendor/luajit/src/lj_bcwrite.c
vendor/luajit/src/lj_load.c
vendor/luajit/src/lj_ir.c
vendor/luajit/src/lj_opt_mem.c
vendor/luajit/src/lj_opt_fold.c
vendor/luajit/src/lj_opt_narrow.c
vendor/luajit/src/lj_opt_dce.c
vendor/luajit/src/lj_opt_loop.c
vendor/luajit/src/lj_opt_split.c
vendor/luajit/src/lj_opt_sink.c
vendor/luajit/src/lj_mcode.c
vendor/luajit/src/lj_snap.c
vendor/luajit/src/lj_record.c
vendor/luajit/src/lj_crecord.c
vendor/luajit/src/lj_ffrecord.c
vendor/luajit/src/lj_asm.c
vendor/luajit/src/lj_trace.c
vendor/luajit/src/lj_gdbjit.c
vendor/luajit/src/lj_ctype.c
vendor/luajit/src/lj_cdata.c
vendor/luajit/src/lj_cconv.c
vendor/luajit/src/lj_ccall.c
vendor/luajit/src/lj_ccallback.c
vendor/luajit/src/lj_carith.c
vendor/luajit/src/lj_clib.c
vendor/luajit/src/lj_cparse.c
vendor/luajit/src/lj_lib.c
vendor/luajit/src/lj_alloc.c
vendor/luajit/src/lib_aux.c
vendor/luajit/src/lib_base.c
vendor/luajit/src/lib_math.c
vendor/luajit/src/lib_bit.c
vendor/luajit/src/lib_string.c
vendor/luajit/src/lib_table.c
vendor/luajit/src/lib_io.c
vendor/luajit/src/lib_os.c
vendor/luajit/src/lib_package.c
vendor/luajit/src/lib_debug.c
vendor/luajit/src/lib_jit.c
vendor/luajit/src/lib_ffi.c
vendor/luajit/src/lib_init.c
)
if(MSVC)
target_sources(luajit PRIVATE vendor/luajit/src/lj_vm.obj)
else(MSVC)
target_sources(luajit PRIVATE vendor/luajit/src/lj_vm.s)
set_property(SOURCE vendor/luajit/src/lj_vm.s PROPERTY LANGUAGE C)
target_link_libraries(luajit dl)
endif(MSVC)
target_include_directories(luajit PRIVATE vendor/luajit/src/gen)
add_dependencies(luajit luajit-buildvm)
target_compile_definitions(luajit PRIVATE LUAJIT_ENABLE_LUA52COMPAT)
add_library(resrc STATIC
src/libresrc/bitmap.cpp
src/libresrc/default_config.cpp
src/libresrc/libresrc.cpp
)
add_dependencies(resrc luajit-minilua)
add_library(csri STATIC
vendor/csri/lib/list.c
vendor/csri/lib/wrap.c
vendor/csri/subhelp/logging.c
)
target_include_directories(csri PRIVATE "vendor/csri/include")
IF (WIN32)
target_include_directories(csri PRIVATE "vendor/csri/lib/win32")
target_sources(csri PRIVATE vendor/csri/lib/win32/enumerate.c)
ELSE()
target_include_directories(csri PRIVATE "vendor/csri/lib/posix")
target_sources(csri PRIVATE vendor/csri/lib/posix/enumerate.c)
ENDIF()
add_executable(Aegisub WIN32
src/command/app.cpp
src/command/audio.cpp
src/command/automation.cpp
src/command/command.cpp
src/command/edit.cpp
src/command/grid.cpp
src/command/help.cpp
src/command/keyframe.cpp
src/command/recent.cpp
src/command/subtitle.cpp
src/command/time.cpp
src/command/timecode.cpp
src/command/tool.cpp
src/command/video.cpp
src/command/vis_tool.cpp
src/dialog_about.cpp
src/dialog_align.cpp
src/dialog_attachments.cpp
src/dialog_automation.cpp
src/dialog_autosave.cpp
src/dialog_colorpicker.cpp
src/dialog_detached_video.cpp
src/dialog_dummy_video.cpp
src/dialog_export.cpp
src/dialog_export_ebu3264.cpp
src/dialog_fonts_collector.cpp
src/dialog_jumpto.cpp
src/dialog_kara_timing_copy.cpp
src/dialog_log.cpp
src/dialog_paste_over.cpp
src/dialog_progress.cpp
src/dialog_properties.cpp
src/dialog_resample.cpp
src/dialog_search_replace.cpp
src/dialog_selected_choices.cpp
src/dialog_selection.cpp
src/dialog_shift_times.cpp
src/dialog_spellchecker.cpp
src/dialog_style_editor.cpp
src/dialog_style_manager.cpp
src/dialog_styling_assistant.cpp
src/dialog_text_import.cpp
src/dialog_timing_processor.cpp
src/dialog_translation.cpp
src/dialog_version_check.cpp
src/dialog_video_details.cpp
src/dialog_video_properties.cpp
src/subtitle_format.cpp
src/subtitle_format_ass.cpp
src/subtitle_format_ebu3264.cpp
src/subtitle_format_encore.cpp
src/subtitle_format_microdvd.cpp
src/subtitle_format_mkv.cpp
src/subtitle_format_srt.cpp
src/subtitle_format_ssa.cpp
src/subtitle_format_transtation.cpp
src/subtitle_format_ttxt.cpp
src/subtitle_format_txt.cpp
src/visual_tool.cpp
src/visual_tool_clip.cpp
src/visual_tool_cross.cpp
src/visual_tool_drag.cpp
src/visual_tool_rotatexy.cpp
src/visual_tool_rotatez.cpp
src/visual_tool_scale.cpp
src/visual_tool_vector_clip.cpp
src/MatroskaParser.c
src/aegisublocale.cpp
src/ass_attachment.cpp
src/ass_dialogue.cpp
src/ass_entry.cpp
src/ass_export_filter.cpp
src/ass_exporter.cpp
src/ass_file.cpp
src/ass_karaoke.cpp
src/ass_override.cpp
src/ass_parser.cpp
src/ass_style.cpp
src/ass_style_storage.cpp
src/async_video_provider.cpp
src/audio_box.cpp
src/audio_colorscheme.cpp
src/audio_controller.cpp
src/audio_display.cpp
src/audio_karaoke.cpp
src/audio_marker.cpp
src/audio_player.cpp
src/audio_provider_factory.cpp
src/audio_renderer.cpp
src/audio_renderer_spectrum.cpp
src/audio_renderer_waveform.cpp
src/audio_timing_dialogue.cpp
src/audio_timing_karaoke.cpp
src/auto4_base.cpp
src/auto4_lua.cpp
src/auto4_lua_assfile.cpp
src/auto4_lua_dialog.cpp
src/auto4_lua_progresssink.cpp
src/base_grid.cpp
src/charset_detect.cpp
src/colorspace.cpp
src/colour_button.cpp
src/compat.cpp
src/context.cpp
src/export_fixstyle.cpp
src/export_framerate.cpp
src/fft.cpp
src/font_file_lister.cpp
src/frame_main.cpp
src/gl_text.cpp
src/gl_wrap.cpp
src/grid_column.cpp
src/help_button.cpp
src/hotkey.cpp
src/hotkey_data_view_model.cpp
src/image_position_picker.cpp
src/initial_line_state.cpp
src/main.cpp
src/menu.cpp
src/mkv_wrap.cpp
src/pen.cpp
src/persist_location.cpp
src/preferences.cpp
src/preferences_base.cpp
src/project.cpp
src/resolution_resampler.cpp
src/search_replace_engine.cpp
src/selection_controller.cpp
src/spellchecker.cpp
src/spline.cpp
src/spline_curve.cpp
src/string_codec.cpp
src/subs_controller.cpp
src/subs_edit_box.cpp
src/subs_edit_ctrl.cpp
src/subs_preview.cpp
src/subtitles_provider.cpp
src/subtitles_provider_libass.cpp
src/text_file_reader.cpp
src/text_file_writer.cpp
src/text_selection_controller.cpp
src/thesaurus.cpp
src/timeedit_ctrl.cpp
src/toggle_bitmap.cpp
src/toolbar.cpp
src/tooltip_manager.cpp
src/utils.cpp
src/validators.cpp
src/vector2d.cpp
src/version.cpp
src/video_box.cpp
src/video_controller.cpp
src/video_display.cpp
src/video_frame.cpp
src/video_out_gl.cpp
src/video_provider_cache.cpp
src/video_provider_dummy.cpp
src/video_provider_manager.cpp
src/video_provider_yuv4mpeg.cpp
src/video_slider.cpp
src/visual_feature.cpp
)
target_link_libraries(Aegisub ${CMAKE_DL_LIBS} libaegisub luabins luajit resrc csri)
if (MSVC)
set_target_properties(libaegisub PROPERTIES COMPILE_FLAGS "/Yu${PROJECT_SOURCE_DIR}/libaegisub/lagi_pre.h" COMPILE_FLAGS "/FI${PROJECT_SOURCE_DIR}/libaegisub/lagi_pre.h")
else(MSVC)
target_compile_options(libaegisub PRIVATE -include "${PROJECT_SOURCE_DIR}/libaegisub/lagi_pre.h")
endif(MSVC)
set_property(
SOURCE libaegisub/unix/path.cpp
PROPERTY COMPILE_DEFINITIONS
P_DATA="${CMAKE_INSTALL_PREFIX}/share/aegisub/"
)
if (MSVC)
add_definitions("-DNOMINMAX -MP -DINITGUID")
set_target_properties(Aegisub PROPERTIES COMPILE_FLAGS "/Yu${PROJECT_SOURCE_DIR}/src/agi_pre.h" COMPILE_FLAGS "/FI${PROJECT_SOURCE_DIR}/src/agi_pre.h")
target_link_libraries (Aegisub Usp10)
#target_sources(Aegisub PRIVATE src/res/res.rc src/res/strings.rc src/crash_writer_minidump.cpp)
target_sources(Aegisub PRIVATE src/res/res.rc src/res/strings.rc src/crash_writer.cpp src/dpi_aware.manifest)
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT Aegisub)
else(MSVC)
target_sources(Aegisub PRIVATE src/crash_writer.cpp)
target_compile_options(Aegisub PRIVATE -include "${PROJECT_SOURCE_DIR}/src/agi_pre.h")
endif(MSVC)
if (WIN32)
target_sources(Aegisub PRIVATE src/font_file_lister_gdi.cpp)
else (WIN32)
find_package(Fontconfig REQUIRED)
target_link_libraries (Aegisub ${Fontconfig_LIBRARIES})
target_sources(Aegisub PRIVATE src/font_file_lister_fontconfig.cpp)
set_property(SOURCE src/font_file_lister_fontconfig.cpp PROPERTY INCLUDE_DIRECTORIES "${Fontconfig_INCLUDE_DIRS}")
endif (WIN32)
find_package(ass REQUIRED)
include_directories(${ass_INCLUDE_DIRS})
target_link_libraries (Aegisub ${ass_LIBRARIES})
find_package(Boost REQUIRED chrono filesystem locale regex system thread)
include_directories(${Boost_INCLUDE_DIRS})
target_link_directories(Aegisub PRIVATE ${Boost_LIBRARY_DIRS})
target_link_libraries(Aegisub ${Boost_LIBRARIES})
find_package(OpenGL REQUIRED)
include_directories(${OPENGL_INCLUDE_DIR})
target_link_libraries (Aegisub ${OPENGL_LIBRARIES})
find_package(Hunspell REQUIRED)
include_directories(${HUNSPELL_INCLUDE_DIR})
target_link_libraries (Aegisub ${HUNSPELL_LIBRARIES})
add_definitions("-DWITH_HUNSPELL")
target_sources(Aegisub PRIVATE src/spellchecker_hunspell.cpp)
find_package(Iconv REQUIRED)
include_directories(${Iconv_INCLUDE_DIRS})
target_link_libraries (Aegisub ${Iconv_LIBRARIES})
add_definitions("-DHAVE_ICONV")
if (NOT Iconv_IS_BUILT_IN)
set_property(
SOURCE libaegisub/common/charset_conv.cpp
PROPERTY COMPILE_DEFINITIONS AGI_ICONV_CONST
)
endif (NOT Iconv_IS_BUILT_IN)
find_package(ICU REQUIRED uc dt in)
include_directories(${ICU_INCLUDE_DIRS})
target_link_libraries (Aegisub ${ICU_LIBRARIES})
find_package(wxWidgets REQUIRED adv base core gl stc xml)
include(${wxWidgets_USE_FILE})
target_link_libraries(Aegisub ${wxWidgets_LIBRARIES})
find_package(ZLIB REQUIRED)
include_directories(${ZLIB_INCLUDE_DIRS})
target_link_libraries (Aegisub ${ZLIB_LIBRARIES})
find_package(ALSA)
if (ALSA_FOUND)
include_directories(${ALSA_INCLUDE_DIRS})
target_link_libraries (Aegisub ${ALSA_LIBRARIES})
add_definitions("-DWITH_ALSA")
target_sources(Aegisub PRIVATE src/audio_player_alsa.cpp)
endif(ALSA_FOUND)
# target_compile_definitions(Aegisub PRIVATE "WITH_AVISYNTH")
# target_sources(Aegisub PRIVATE src/audio_provider_avs.cpp src/avisynth_wrap.cpp src/video_provider_avs.cpp)
target_compile_definitions(Aegisub PRIVATE "WITH_CSRI")
target_sources(Aegisub PRIVATE src/subtitles_provider_csri.cpp)
set_property(SOURCE src/subtitles_provider_csri.cpp PROPERTY INCLUDE_DIRECTORIES "${PROJECT_SOURCE_DIR}/vendor/csri/include")
if(MSVC)
target_link_libraries (Aegisub dsound)
add_definitions("-DWITH_DIRECTSOUND")
target_sources(Aegisub PRIVATE src/audio_player_dsound.cpp src/audio_player_dsound2.cpp)
endif(MSVC)
find_package(FFMS2)
if (FFMS2_FOUND)
include_directories(${FFMS2_INCLUDE_DIRS})
target_link_libraries (Aegisub ${FFMS2_LIBRARIES})
add_definitions("-DWITH_FFMS2")
target_sources(Aegisub PRIVATE src/audio_provider_ffmpegsource.cpp src/ffmpegsource_common.cpp src/video_provider_ffmpegsource.cpp)
endif(FFMS2_FOUND)
find_package(FFTW)
if (FFTW_FOUND)
include_directories(${FFTW_INCLUDES})
target_link_libraries (Aegisub ${FFTW_LIBRARIES})
add_definitions("-DWITH_FFTW3")
endif(FFTW_FOUND)
#ifdef WITH_LIBPULSE
#add_definitions("-DWITH_LIBPULSE")
#target_sources(Aegisub PRIVATE src/audio_player_pulse.cpp)
find_package(OpenAL)
if (OPENAL_FOUND)
include_directories(${OPENAL_INCLUDE_DIR})
target_link_libraries (Aegisub ${OPENAL_LIBRARY})
add_definitions("-DWITH_OPENAL")
target_sources(Aegisub PRIVATE src/audio_player_openal.cpp)
endif(OPENAL_FOUND)
#ifdef WITH_OSS
#ifdef WITH_PORTAUDIO
#ifdef WITH_STARTUPLOG
find_package(uchardet)
if (uchardet_FOUND)
include_directories(${uchardet_INCLUDE_DIRS})
target_link_libraries (Aegisub ${uchardet_LIBRARIES})
add_definitions("-DWITH_UCHARDET")
endif(uchardet_FOUND)
#ifdef WITH_UPDATE_CHECKER

View File

@ -23,9 +23,13 @@ The `cibuilds` branch makes some CI builds of snapshots of `feature` at relevant
- [`lua_api`](https://github.com/arch1t3cht/Aegisub/tree/lua_api): Add new functions to the Lua automation API, like controlling the selection or cursor in the text edit box
- [`vector_clip_actions`](https://github.com/arch1t3cht/Aegisub/tree/vector_clip_actions): Make the different modes of the vector clip tool (lines, bezier curves, adding points, etc) bindable to hotkeys
- [`color_picker_fix2`](https://github.com/arch1t3cht/Aegisub/tree/color_picker_fix2): Add an option (under "Interface") to restrict the color picker to the window, which fixes the color picker on Linux in a lot of cases.
- [`avisynth`](https://github.com/arch1t3cht/Aegisub/tree/avisynth): Reenable Avisynth support on Windows (Still occasionally crashes)
- [`bugfixes`](https://github.com/arch1t3cht/Aegisub/tree/bugfixes): Various fixes, mostly relevant for compilation
- [`avisynth`](https://github.com/arch1t3cht/Aegisub/tree/avisynth): Reenable Avisynth support on Windows and enable Avisynth on Linux
- [`bestsource`](https://github.com/arch1t3cht/Aegisub/tree/bestsource): Add BestSource audio and video source. This source is slower than others by multiple orders of magnitude, but in exchange it can guarantee exact seeking.
- [`vapoursynth`](https://github.com/arch1t3cht/Aegisub/tree/vapourssynth): Add Vapoursynth audio and video source
- [`bugfixes`](https://github.com/arch1t3cht/Aegisub/tree/bugfixes): Various fixes necessary for compilation. Most branches are based on this.
- [`fixes`](https://github.com/arch1t3cht/Aegisub/tree/fixes): Miscellaneous bugfixes
- [`misc_dc`](https://github.com/arch1t3cht/Aegisub/tree/misc_dc): Miscellaneous changes taken from AegisubDC
- [`xa-ds2`](https://github.com/arch1t3cht/Aegisub/tree/xa-ds2): Add XAudio2 backend and allow stereo playback for some other backends, by wangqr and Shinon.
- [`video_panning_feature`](https://github.com/arch1t3cht/Aegisub/tree/video_panning_feature): Merge [moex3's video zoom and panning](https://github.com/TypesettingTools/Aegisub/pull/150), with an OSX fix and more options to control zoom behavior
- [`spectrum-frequency-mapping`](https://github.com/arch1t3cht/Aegisub/tree/spectrum-frequency-mapping): Merge EleonoreMizo's [spectrum display improvements](https://github.com/TypesettingTools/Aegisub/pull/94), and also make Shift+Scroll vertically zoom the audio display
- [`wangqr_time_video`](https://github.com/arch1t3cht/Aegisub/tree/wangqr_time_video): Merge wangqr's feature adding a tool for timing subtitles to changes in the video
@ -43,7 +47,9 @@ The exact way of switching depends on your Linux distribution, but essentially y
The changes to `default_config.json` or similar files weren't detected by meson due to missing regen dependencies. You can either merge the `bugfixes` branch or rebuild from scratch.
#### The video is desynced / Frames don't appear at the right time
This is probably due to the ffms2 seeking bug ([#394](https://github.com/FFMS/ffms2/issues/394)). On Windows, this shouldn't happen anymore. On Linux, you need to install the latest git version of ffms2 - for example the [`ffms2-git`](https://aur.archlinux.org/packages/ffms2-git) AUR package on Arch linux, or just compile it yourself.
This is probably due to the ffms2 seeking bug ([#394](https://github.com/FFMS/ffms2/issues/394)). On Windows, this specific regression shouldn't happen anymore. On Linux, you need to install the latest git version of ffms2 - for example the [`ffms2-git`](https://aur.archlinux.org/packages/ffms2-git) AUR package on Arch linux, or just compile it yourself.
If it's not because of this particular bug, you can also try an alternative video source like LSMASHSource via Avisynth or Vapoursynth, or BestSource.
#### On Windows: Aegisub crashes whenever I open a video
If you're compiling yourself, try adding `--force-fallback-for=zlib` to the meson options.

View File

@ -21,13 +21,107 @@
#include "libaegisub/log.h"
#include "libaegisub/util.h"
namespace agi {
void AudioProvider::GetAudioWithVolume(void *buf, int64_t start, int64_t count, double volume) const {
GetAudio(buf, start, count);
namespace {
template<typename Source>
class ConvertFloatToInt16 {
Source* src;
public:
ConvertFloatToInt16(Source* src) :src(src) {}
int16_t operator[](size_t idx) const {
Source expanded = src[idx] * 32768;
return expanded < -32768 ? -32768 :
expanded > 32767 ? 32767 :
static_cast<int16_t>(expanded);
}
};
// 8 bits per sample is assumed to be unsigned with a bias of 128,
// while everything else is assumed to be signed with zero bias
class ConvertIntToInt16 {
void* src;
int bytes_per_sample;
public:
ConvertIntToInt16(void* src, int bytes_per_sample) :src(src), bytes_per_sample(bytes_per_sample) {}
const int16_t& operator[](size_t idx) const {
return *reinterpret_cast<int16_t*>(reinterpret_cast<char*>(src) + (idx + 1) * bytes_per_sample - sizeof(int16_t));
}
};
class ConvertUInt8ToInt16 {
uint8_t* src;
public:
ConvertUInt8ToInt16(uint8_t* src) :src(src) {}
int16_t operator[](size_t idx) const {
return int16_t(src[idx]-128) << 8;
}
};
template<typename Source>
class DownmixToMono {
Source src;
int channels;
public:
DownmixToMono(Source src, int channels) :src(src), channels(channels) {}
int16_t operator[](size_t idx) const {
int ret = 0;
// Just average the channels together
for (int i = 0; i < channels; ++i)
ret += src[idx * channels + i];
return ret / channels;
}
};
}
namespace agi {
void AudioProvider::FillBufferInt16Mono(int16_t* buf, int64_t start, int64_t count) const {
if (!float_samples && bytes_per_sample == 2 && channels == 1) {
FillBuffer(buf, start, count);
return;
}
void* buff = malloc(bytes_per_sample * count * channels);
FillBuffer(buff, start, count);
if (channels == 1) {
if (float_samples) {
if (bytes_per_sample == sizeof(float))
for (int64_t i = 0; i < count; ++i)
buf[i] = ConvertFloatToInt16<float>(reinterpret_cast<float*>(buff))[i];
else if (bytes_per_sample == sizeof(double))
for (int64_t i = 0; i < count; ++i)
buf[i] = ConvertFloatToInt16<double>(reinterpret_cast<double*>(buff))[i];
}
else {
if (bytes_per_sample == sizeof(uint8_t))
for (int64_t i = 0; i < count; ++i)
buf[i] = ConvertUInt8ToInt16(reinterpret_cast<uint8_t*>(buff))[i];
else
for (int64_t i = 0; i < count; ++i)
buf[i] = ConvertIntToInt16(buff, bytes_per_sample)[i];
}
}
else {
if (float_samples) {
if (bytes_per_sample == sizeof(float))
for (int64_t i = 0; i < count; ++i)
buf[i] = DownmixToMono<ConvertFloatToInt16<float> >(ConvertFloatToInt16<float>(reinterpret_cast<float*>(buff)), channels)[i];
else if (bytes_per_sample == sizeof(double))
for (int64_t i = 0; i < count; ++i)
buf[i] = DownmixToMono<ConvertFloatToInt16<double> >(ConvertFloatToInt16<double>(reinterpret_cast<double*>(buff)), channels)[i];
}
else {
if (bytes_per_sample == sizeof(uint8_t))
for (int64_t i = 0; i < count; ++i)
buf[i] = DownmixToMono<ConvertUInt8ToInt16>(ConvertUInt8ToInt16(reinterpret_cast<uint8_t*>(buff)), channels)[i];
else
for (int64_t i = 0; i < count; ++i)
buf[i] = DownmixToMono<ConvertIntToInt16>(ConvertIntToInt16(buff, bytes_per_sample), channels)[i];
}
}
free(buff);
}
void AudioProvider::GetInt16MonoAudioWithVolume(int16_t *buf, int64_t start, int64_t count, double volume) const {
GetInt16MonoAudio(buf, start, count);
if (volume == 1.0) return;
if (bytes_per_sample != 2)
throw agi::InternalError("GetAudioWithVolume called on unconverted audio stream");
auto buffer = static_cast<int16_t *>(buf);
for (size_t i = 0; i < (size_t)count; ++i)
@ -75,6 +169,39 @@ void AudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const {
}
}
void AudioProvider::GetInt16MonoAudio(int16_t* buf, int64_t start, int64_t count) const {
if (start < 0) {
memset(buf, 0, sizeof(int16_t) * std::min(-start, count));
buf -= start;
count += start;
start = 0;
}
if (start + count > num_samples) {
int64_t zero_count = std::min(count, start + count - num_samples);
count -= zero_count;
memset(buf + count, 0, sizeof(int16_t) * zero_count);
}
if (count <= 0) return;
try {
FillBufferInt16Mono(buf, start, count);
}
catch (AudioDecodeError const& e) {
// We don't have any good way to report errors here, so just log the
// failure and return silence
LOG_E("audio_provider") << e.GetMessage();
memset(buf, 0, sizeof(int16_t) * count);
return;
}
catch (...) {
LOG_E("audio_provider") << "Unknown audio decoding error";
memset(buf, 0, sizeof(int16_t) * count);
return;
}
}
namespace {
class writer {
io::Save outfile;
@ -114,7 +241,7 @@ void SaveAudioClip(AudioProvider const& provider, fs::path const& path, int star
out.write("WAVEfmt ");
out.write<int32_t>(16); // Size of chunk
out.write<int16_t>(1); // compression format (PCM)
out.write<int16_t>(provider.AreSamplesFloat() ? 3 : 1); // compression format (1: WAVE_FORMAT_PCM, 3: WAVE_FORMAT_IEEE_FLOAT)
out.write<int16_t>(provider.GetChannels());
out.write<int32_t>(provider.GetSampleRate());
out.write<int32_t>(provider.GetSampleRate() * provider.GetChannels() * provider.GetBytesPerSample());
@ -134,4 +261,4 @@ void SaveAudioClip(AudioProvider const& provider, fs::path const& path, int star
out.write(buf);
}
}
}
}

View File

@ -22,119 +22,19 @@
#include <limits>
using namespace agi;
/// Anything integral -> 16 bit signed machine-endian audio converter
namespace {
template<class Target>
class BitdepthConvertAudioProvider final : public AudioProviderWrapper {
int src_bytes_per_sample;
mutable std::vector<uint8_t> src_buf;
class ConvertAudioProvider final : public AudioProviderWrapper {
public:
BitdepthConvertAudioProvider(std::unique_ptr<AudioProvider> src) : AudioProviderWrapper(std::move(src)) {
if (bytes_per_sample > 8)
throw AudioProviderError("Audio format converter: audio with bitdepths greater than 64 bits/sample is currently unsupported");
src_bytes_per_sample = bytes_per_sample;
bytes_per_sample = sizeof(Target);
}
void FillBuffer(void *buf, int64_t start, int64_t count64) const override {
auto count = static_cast<size_t>(count64);
assert(count == count64);
src_buf.resize(count * src_bytes_per_sample * channels);
source->GetAudio(src_buf.data(), start, count);
auto dest = static_cast<int16_t*>(buf);
for (int64_t i = 0; i < count * channels; ++i) {
int64_t sample = 0;
// 8 bits per sample is assumed to be unsigned with a bias of 127,
// while everything else is assumed to be signed with zero bias
if (src_bytes_per_sample == 1)
sample = src_buf[i] - 128;
else {
for (int j = src_bytes_per_sample; j > 0; --j) {
sample <<= 8;
sample += src_buf[i * src_bytes_per_sample + j - 1];
}
}
if (static_cast<size_t>(src_bytes_per_sample) > sizeof(Target))
sample /= 1LL << (src_bytes_per_sample - sizeof(Target)) * 8;
else if (static_cast<size_t>(src_bytes_per_sample) < sizeof(Target))
sample *= 1LL << (sizeof(Target) - src_bytes_per_sample ) * 8;
dest[i] = static_cast<Target>(sample);
}
}
};
/// Floating point -> 16 bit signed machine-endian audio converter
template<class Source, class Target>
class FloatConvertAudioProvider final : public AudioProviderWrapper {
mutable std::vector<Source> src_buf;
public:
FloatConvertAudioProvider(std::unique_ptr<AudioProvider> src) : AudioProviderWrapper(std::move(src)) {
bytes_per_sample = sizeof(Target);
ConvertAudioProvider(std::unique_ptr<AudioProvider> src) : AudioProviderWrapper(std::move(src)) {
float_samples = false;
}
void FillBuffer(void *buf, int64_t start, int64_t count64) const override {
auto count = static_cast<size_t>(count64);
assert(count == count64);
src_buf.resize(count * channels);
source->GetAudio(&src_buf[0], start, count);
auto dest = static_cast<Target*>(buf);
for (size_t i = 0; i < static_cast<size_t>(count * channels); ++i) {
Source expanded;
if (src_buf[i] < 0)
expanded = static_cast<Target>(-src_buf[i] * std::numeric_limits<Target>::min());
else
expanded = static_cast<Target>(src_buf[i] * std::numeric_limits<Target>::max());
dest[i] = expanded < std::numeric_limits<Target>::min() ? std::numeric_limits<Target>::min() :
expanded > std::numeric_limits<Target>::max() ? std::numeric_limits<Target>::max() :
static_cast<Target>(expanded);
}
}
};
/// Non-mono 16-bit signed machine-endian -> mono 16-bit signed machine endian converter
class DownmixAudioProvider final : public AudioProviderWrapper {
int src_channels;
mutable std::vector<int16_t> src_buf;
public:
DownmixAudioProvider(std::unique_ptr<AudioProvider> src) : AudioProviderWrapper(std::move(src)) {
src_channels = channels;
channels = 1;
bytes_per_sample = sizeof(int16_t);
}
void FillBuffer(void *buf, int64_t start, int64_t count64) const override {
auto count = static_cast<size_t>(count64);
assert(count == count64);
src_buf.resize(count * src_channels);
source->GetAudio(&src_buf[0], start, count);
auto dst = static_cast<int16_t*>(buf);
// Just average the channels together
while (count-- > 0) {
int sum = 0;
for (int c = 0; c < src_channels; ++c)
sum += src_buf[count * src_channels + c];
dst[count] = static_cast<int16_t>(sum / src_channels);
}
void FillBuffer(void *buf, int64_t start, int64_t count) const override {
source->GetInt16MonoAudio(reinterpret_cast<int16_t*>(buf), start, count);
}
};
/// Sample doubler with linear interpolation for the samples provider
/// Requires 16-bit mono input
class SampleDoublingAudioProvider final : public AudioProviderWrapper {
@ -177,26 +77,23 @@ std::unique_ptr<AudioProvider> CreateConvertAudioProvider(std::unique_ptr<AudioP
// Ensure 16-bit audio with proper endianness
if (provider->AreSamplesFloat()) {
LOG_D("audio_provider") << "Converting float to S16";
if (provider->GetBytesPerSample() == sizeof(float))
provider = agi::make_unique<FloatConvertAudioProvider<float, int16_t>>(std::move(provider));
else
provider = agi::make_unique<FloatConvertAudioProvider<double, int16_t>>(std::move(provider));
}
if (provider->GetBytesPerSample() != 2) {
LOG_D("audio_provider") << "Converting " << provider->GetBytesPerSample() << " bytes per sample or wrong endian to S16";
provider = agi::make_unique<BitdepthConvertAudioProvider<int16_t>>(std::move(provider));
LOG_D("audio_provider") << "Converting " << provider->GetBytesPerSample() << " bytes per sample to S16";
}
// We currently only support mono audio
if (provider->GetChannels() != 1) {
LOG_D("audio_provider") << "Downmixing to mono from " << provider->GetChannels() << " channels";
provider = agi::make_unique<DownmixAudioProvider>(std::move(provider));
}
// Some players don't like low sample rate audio
while (provider->GetSampleRate() < 32000) {
LOG_D("audio_provider") << "Doubling sample rate";
provider = agi::make_unique<SampleDoublingAudioProvider>(std::move(provider));
if (provider->GetSampleRate() < 32000) {
provider = agi::make_unique<ConvertAudioProvider>(std::move(provider));
while (provider->GetSampleRate() < 32000) {
LOG_D("audio_provider") << "Doubling sample rate";
provider = agi::make_unique<SampleDoublingAudioProvider>(std::move(provider));
}
}
return provider;

View File

@ -43,15 +43,15 @@ class HDAudioProvider final : public AudioProviderWrapper {
}
if (count > 0) {
start *= bytes_per_sample;
count *= bytes_per_sample;
start *= bytes_per_sample * channels;
count *= bytes_per_sample * channels;
memcpy(buf, file.read(start, count), count);
}
}
fs::path CacheFilename(fs::path const& dir) {
// Check free space
if ((uint64_t)num_samples * bytes_per_sample > fs::FreeSpace(dir))
if ((uint64_t)num_samples * bytes_per_sample * channels > fs::FreeSpace(dir))
throw AudioProviderError("Not enough free disk space in " + dir.string() + " to cache the audio");
return format("audio-%lld-%lld", time(nullptr),
@ -61,7 +61,7 @@ class HDAudioProvider final : public AudioProviderWrapper {
public:
HDAudioProvider(std::unique_ptr<AudioProvider> src, agi::fs::path const& dir)
: AudioProviderWrapper(std::move(src))
, file(dir / CacheFilename(dir), num_samples * bytes_per_sample)
, file(dir / CacheFilename(dir), num_samples * bytes_per_sample * channels)
{
decoded_samples = 0;
decoder = std::thread([&] {

View File

@ -29,6 +29,11 @@ class LockAudioProvider final : public agi::AudioProviderWrapper {
source->GetAudio(buf, start, count);
}
void FillBufferInt16Mono(int16_t *buf, int64_t start, int64_t count) const override {
std::unique_lock<std::mutex> lock(mutex);
source->GetInt16MonoAudio(buf, start, count);
}
public:
LockAudioProvider(std::unique_ptr<AudioProvider> src)
: AudioProviderWrapper(std::move(src))

View File

@ -46,14 +46,14 @@ public:
decoded_samples = 0;
try {
blockcache.resize((source->GetNumSamples() * source->GetBytesPerSample() + CacheBlockSize - 1) >> CacheBits);
blockcache.resize((num_samples * bytes_per_sample * channels + CacheBlockSize - 1) >> CacheBits);
}
catch (std::bad_alloc const&) {
throw AudioProviderError("Not enough memory available to cache in RAM");
}
decoder = std::thread([&] {
int64_t readsize = CacheBlockSize / source->GetBytesPerSample();
int64_t readsize = CacheBlockSize / bytes_per_sample / channels;
for (size_t i = 0; i < blockcache.size(); i++) {
if (cancelled) break;
auto actual_read = std::min<int64_t>(readsize, num_samples - i * readsize);
@ -71,20 +71,22 @@ public:
void RAMAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const {
auto charbuf = static_cast<char *>(buf);
for (int64_t bytes_remaining = count * bytes_per_sample; bytes_remaining; ) {
for (int64_t bytes_remaining = count * bytes_per_sample * channels; bytes_remaining; ) {
if (start >= decoded_samples) {
memset(charbuf, 0, bytes_remaining);
break;
}
const int i = (start * bytes_per_sample) >> CacheBits;
const int start_offset = (start * bytes_per_sample) & (CacheBlockSize-1);
const int read_size = std::min<int>(bytes_remaining, CacheBlockSize - start_offset);
const int64_t samples_per_block = CacheBlockSize / bytes_per_sample / channels;
const size_t i = start / samples_per_block;
const int start_offset = (start % samples_per_block) * bytes_per_sample * channels;
const int read_size = std::min<int>(bytes_remaining, samples_per_block * bytes_per_sample * channels - start_offset);
memcpy(charbuf, &blockcache[i][start_offset], read_size);
charbuf += read_size;
bytes_remaining -= read_size;
start += read_size / bytes_per_sample;
start += read_size / bytes_per_sample / channels;
}
}
}
@ -93,4 +95,4 @@ namespace agi {
std::unique_ptr<AudioProvider> CreateRAMAudioProvider(std::unique_ptr<AudioProvider> src) {
return agi::make_unique<RAMAudioProvider>(std::move(src));
}
}
}

View File

@ -20,8 +20,8 @@
#include <libaegisub/fs_fwd.h>
#include <atomic>
#include <memory>
#include <vector>
#include <memory>
namespace agi {
class AudioProvider {
@ -37,6 +37,7 @@ protected:
bool float_samples = false;
virtual void FillBuffer(void *buf, int64_t start, int64_t count) const = 0;
virtual void FillBufferInt16Mono(int16_t* buf, int64_t start, int64_t count) const;
void ZeroFill(void *buf, int64_t count) const;
@ -44,7 +45,8 @@ public:
virtual ~AudioProvider() = default;
void GetAudio(void *buf, int64_t start, int64_t count) const;
void GetAudioWithVolume(void *buf, int64_t start, int64_t count, double volume) const;
void GetInt16MonoAudio(int16_t* buf, int64_t start, int64_t count) const;
void GetInt16MonoAudioWithVolume(int16_t *buf, int64_t start, int64_t count, double volume) const;
int64_t GetNumSamples() const { return num_samples; }
int64_t GetDecodedSamples() const { return decoded_samples; }

View File

@ -7,7 +7,7 @@ project('Aegisub', ['c', 'cpp'],
cmake = import('cmake')
if host_machine.system() == 'windows'
add_project_arguments('-DNOMINMAX', '-D_WIN32_WINNT=0x0601', language: 'cpp')
add_project_arguments('-DNOMINMAX', language: 'cpp')
if not get_option('csri').disabled()
add_global_arguments('-DCSRI_NO_EXPORT', language: 'c')
@ -224,6 +224,24 @@ foreach dep: [
endif
endforeach
needs_ffmpeg = false
if get_option('bestsource').enabled()
conf.set('WITH_BESTSOURCE', 1)
bs = subproject('bestsource')
deps += bs.get_variable('bestsource_dep')
dep_avail += 'BestSource'
needs_ffmpeg = true
endif
if needs_ffmpeg
conf.set('WITH_FFMPEG', 1)
deps += [
dependency('libavutil', default_options: ['tests=disabled']),
dependency('libswscale', default_options: ['tests=disabled']),
]
endif
if get_option('avisynth').enabled()
conf.set('WITH_AVISYNTH', 1) # bundled separately with installer
dep_avail += 'AviSynth'
@ -241,20 +259,51 @@ if get_option('avisynth').enabled()
endif
endif
if host_machine.system() == 'windows' and not get_option('directsound').disabled()
dsound_dep = cc.find_library('dsound', required: get_option('directsound'))
winmm_dep = cc.find_library('winmm', required: get_option('directsound'))
ole32_dep = cc.find_library('ole32', required: get_option('directsound'))
have_dsound_h = cc.has_header('dsound.h')
if not have_dsound_h and get_option('directsound').enabled()
error('DirectSound enabled but dsound.h not found')
if get_option('vapoursynth').enabled()
conf.set('WITH_VAPOURSYNTH', 1)
vs_sub = subproject('vapoursynth')
deps_inc += vs_sub.get_variable('vs_inc')
dep_avail += 'VapourSynth'
endif
if host_machine.system() == 'windows'
if not get_option('directsound').disabled()
dsound_dep = cc.find_library('dsound', required: get_option('directsound'))
winmm_dep = cc.find_library('winmm', required: get_option('directsound'))
ole32_dep = cc.find_library('ole32', required: get_option('directsound'))
have_dsound_h = cc.has_header('dsound.h')
if not have_dsound_h and get_option('directsound').enabled()
error('DirectSound enabled but dsound.h not found')
endif
dxguid_dep = cc.find_library('dxguid', required: true)
if dsound_dep.found() and winmm_dep.found() and ole32_dep.found() and dxguid_dep.found() and have_dsound_h
deps += [dsound_dep, winmm_dep, ole32_dep, dxguid_dep]
conf.set('WITH_DIRECTSOUND', 1)
dep_avail += 'DirectSound'
endif
endif
dxguid_dep = cc.find_library('dxguid', required: true)
if dsound_dep.found() and winmm_dep.found() and ole32_dep.found() and dxguid_dep.found() and have_dsound_h
deps += [dsound_dep, winmm_dep, ole32_dep, dxguid_dep]
conf.set('WITH_DIRECTSOUND', 1)
dep_avail += 'DirectSound'
if not get_option('xaudio2').disabled()
have_xaudio_h = cc.has_header('xaudio2.h')
xaudio2_dep = cc.find_library('xaudio2', required: true)
if have_xaudio_h and xaudio2_dep.found()
deps += [xaudio2_dep]
conf.set('WITH_XAUDIO2', 1)
dep_avail += 'XAudio2'
# XAudio2 needs Windows 8 or newer, so we tell meson not to define an older windows or else it can break things.
add_project_arguments('-D_WIN32_WINNT=0x0602', language: 'cpp')
else
# Windows 8 not required if XAudio2 fails to be found. revert for compat.
add_project_arguments('-D_WIN32_WINNT=0x0601', language: 'cpp')
endif
if not have_dsound_h and get_option('xaudio2').enabled()
error('xaudio2 enabled but xaudio2.h not found')
endif
else
# Windows 8 not required if XAudio2 is disabled. revert for compat.
add_project_arguments('-D_WIN32_WINNT=0x0601', language: 'cpp')
endif
endif

View File

@ -3,10 +3,13 @@ option('openal', type: 'feature', description: 'OpenAL audio output')
option('libpulse', type: 'feature', description: 'PulseAudio audio output')
option('portaudio', type: 'feature', description: 'PortAudio audio output')
option('directsound', type: 'feature', description: 'DirectSound audio output')
option('default_audio_output', type: 'combo', choices: ['auto', 'ALSA', 'OpenAL', 'PulseAudio', 'PortAudio', 'DirectSound'], description: 'Default audio output')
option('xaudio2', type: 'feature', description: 'XAudio2 audio output')
option('default_audio_output', type: 'combo', choices: ['auto', 'ALSA', 'OpenAL', 'PulseAudio', 'PortAudio', 'DirectSound', 'XAudio2'], description: 'Default audio output')
option('ffms2', type: 'feature', description: 'FFMS2 video source')
option('avisynth', type: 'feature', description: 'AviSynth video source')
option('bestsource', type: 'feature', description: 'BestSource video source')
option('vapoursynth', type: 'feature', description: 'VapourSynth video source')
option('fftw3', type: 'feature', description: 'FFTW3 support')
option('hunspell', type: 'feature', description: 'Hunspell spell checker')

View File

@ -29,6 +29,7 @@ Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes";
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".wav"; ValueData: ""; Flags: uninsdeletekey
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".ogg"; ValueData: ""; Flags: uninsdeletekey
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".avs"; ValueData: ""; Flags: uninsdeletekey
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".vpy"; ValueData: ""; Flags: uninsdeletekey
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".opus"; ValueData: ""; Flags: uninsdeletekey
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".h264"; ValueData: ""; Flags: uninsdeletekey
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".hevc"; ValueData: ""; Flags: uninsdeletekey
@ -165,6 +166,7 @@ Root: HKLM; Subkey: "SOFTWARE\Classes\.m4a\OpenWithProgids"; ValueType: string;
Root: HKLM; Subkey: "SOFTWARE\Classes\.wav\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Audio.1"; Flags: uninsdeletevalue
Root: HKLM; Subkey: "SOFTWARE\Classes\.ogg\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Media.1"; Flags: uninsdeletevalue
Root: HKLM; Subkey: "SOFTWARE\Classes\.avs\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Video.1"; Flags: uninsdeletevalue
Root: HKLM; Subkey: "SOFTWARE\Classes\.vpy\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Media.1"; Flags: uninsdeletevalue
Root: HKLM; Subkey: "SOFTWARE\Classes\.opus\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Audio.1"; Flags: uninsdeletevalue
Root: HKLM; Subkey: "SOFTWARE\Classes\.h264\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Video.1"; Flags: uninsdeletevalue
Root: HKLM; Subkey: "SOFTWARE\Classes\.hevc\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Video.1"; Flags: uninsdeletevalue

View File

@ -43,6 +43,7 @@
std::unique_ptr<AudioPlayer> CreateAlsaPlayer(agi::AudioProvider *providers, wxWindow *window);
std::unique_ptr<AudioPlayer> CreateDirectSoundPlayer(agi::AudioProvider *providers, wxWindow *window);
std::unique_ptr<AudioPlayer> CreateDirectSound2Player(agi::AudioProvider *providers, wxWindow *window);
std::unique_ptr<AudioPlayer> CreateXAudio2Player(agi::AudioProvider *providers, wxWindow *window);
std::unique_ptr<AudioPlayer> CreateOpenALPlayer(agi::AudioProvider *providers, wxWindow *window);
std::unique_ptr<AudioPlayer> CreatePortAudioPlayer(agi::AudioProvider *providers, wxWindow *window);
std::unique_ptr<AudioPlayer> CreatePulseAudioPlayer(agi::AudioProvider *providers, wxWindow *window);
@ -63,6 +64,9 @@ namespace {
{"DirectSound-old", CreateDirectSoundPlayer, false},
{"DirectSound", CreateDirectSound2Player, false},
#endif
#ifdef WITH_XAUDIO2
{"Xaudio2", CreateXAudio2Player, false},
#endif
#ifdef WITH_OPENAL
{"OpenAL", CreateOpenALPlayer, false},
#endif

View File

@ -127,7 +127,7 @@ void AlsaPlayer::PlaybackThread()
do_setup:
snd_pcm_format_t pcm_format;
switch (provider->GetBytesPerSample())
switch (/*provider->GetBytesPerSample()*/ sizeof(int16_t))
{
case 1:
LOG_D("audio/player/alsa") << "format U8";
@ -143,7 +143,7 @@ do_setup:
if (snd_pcm_set_params(pcm,
pcm_format,
SND_PCM_ACCESS_RW_INTERLEAVED,
provider->GetChannels(),
/*provider->GetChannels()*/ 1,
provider->GetSampleRate(),
1, // allow resample
100*1000 // 100 milliseconds latency
@ -151,7 +151,8 @@ do_setup:
return;
LOG_D("audio/player/alsa") << "set pcm params";
size_t framesize = provider->GetChannels() * provider->GetBytesPerSample();
//size_t framesize = provider->GetChannels() * provider->GetBytesPerSample();
size_t framesize = sizeof(int16_t);
while (true)
{
@ -175,7 +176,7 @@ do_setup:
{
auto avail = std::min(snd_pcm_avail(pcm), (snd_pcm_sframes_t)(end_position-position));
decode_buffer.resize(avail * framesize);
provider->GetAudioWithVolume(decode_buffer.data(), position, avail, volume);
provider->GetInt16MonoAudioWithVolume(reinterpret_cast<int16_t*>(decode_buffer.data()), position, avail, volume);
snd_pcm_sframes_t written = 0;
while (written <= 0)
@ -235,7 +236,7 @@ do_setup:
{
decode_buffer.resize(avail * framesize);
provider->GetAudioWithVolume(decode_buffer.data(), position, avail, volume);
provider->GetInt16MonoAudioWithVolume(reinterpret_cast<int16_t*>(decode_buffer.data()), position, avail, volume);
snd_pcm_sframes_t written = 0;
while (written <= 0)
{
@ -352,4 +353,4 @@ std::unique_ptr<AudioPlayer> CreateAlsaPlayer(agi::AudioProvider *provider, wxWi
return agi::make_unique<AlsaPlayer>(provider);
}
#endif // WITH_ALSA
#endif // WITH_ALSA

View File

@ -45,6 +45,7 @@
#include <mmsystem.h>
#include <dsound.h>
#include <cguid.h>
namespace {
class DirectSoundPlayer;
@ -111,8 +112,10 @@ DirectSoundPlayer::DirectSoundPlayer(agi::AudioProvider *provider, wxWindow *par
WAVEFORMATEX waveFormat;
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
waveFormat.nSamplesPerSec = provider->GetSampleRate();
waveFormat.nChannels = provider->GetChannels();
waveFormat.wBitsPerSample = provider->GetBytesPerSample() * 8;
//waveFormat.nChannels = provider->GetChannels();
//waveFormat.wBitsPerSample = provider->GetBytesPerSample() * 8;
waveFormat.nChannels = 1;
waveFormat.wBitsPerSample = sizeof(int16_t) * 8;
waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8;
waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
waveFormat.cbSize = sizeof(waveFormat);
@ -160,7 +163,7 @@ bool DirectSoundPlayer::FillBuffer(bool fill) {
HRESULT res;
void *ptr1, *ptr2;
unsigned long int size1, size2;
int bytesps = provider->GetBytesPerSample();
int bytesps = /*provider->GetBytesPerSample()*/ sizeof(int16_t);
// To write length
int toWrite = 0;
@ -223,8 +226,8 @@ RetryLock:
LOG_D_IF(!count1 && !count2, "audio/player/dsound1") << "DS fill: nothing";
// Get source wave
if (count1) provider->GetAudioWithVolume(ptr1, playPos, count1, volume);
if (count2) provider->GetAudioWithVolume(ptr2, playPos+count1, count2, volume);
if (count1) provider->GetInt16MonoAudioWithVolume(reinterpret_cast<int16_t*>(ptr1), playPos, count1, volume);
if (count2) provider->GetInt16MonoAudioWithVolume(reinterpret_cast<int16_t*>(ptr2), playPos+count1, count2, volume);
playPos += count1+count2;
buffer->Unlock(ptr1,count1*bytesps,ptr2,count2*bytesps);
@ -254,7 +257,7 @@ void DirectSoundPlayer::Play(int64_t start,int64_t count) {
FillBuffer(true);
DWORD play_flag = 0;
if (count*provider->GetBytesPerSample() > bufSize) {
if (count*/*provider->GetBytesPerSample()*/sizeof(int16_t) > bufSize) {
// Start thread
thread = new DirectSoundPlayerThread(this);
thread->Create();
@ -371,4 +374,4 @@ std::unique_ptr<AudioPlayer> CreateDirectSoundPlayer(agi::AudioProvider *provide
return agi::make_unique<DirectSoundPlayer>(provider, parent);
}
#endif // WITH_DIRECTSOUND
#endif // WITH_DIRECTSOUND

View File

@ -317,13 +317,14 @@ void DirectSoundPlayer2Thread::Run()
// Describe the wave format
WAVEFORMATEX waveFormat;
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
waveFormat.nSamplesPerSec = provider->GetSampleRate();
waveFormat.cbSize = 0;
waveFormat.wFormatTag = provider->AreSamplesFloat() ? 3 : WAVE_FORMAT_PCM; // Eh fuck it.
waveFormat.nChannels = provider->GetChannels();
waveFormat.wBitsPerSample = provider->GetBytesPerSample() * 8;
waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8;
waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
waveFormat.cbSize = sizeof(waveFormat);
//waveFormat.cbSize = sizeof(waveFormat);
// And the buffer itself
int aim = waveFormat.nAvgBytesPerSec * (wanted_latency*buffer_length)/1000;
@ -332,7 +333,7 @@ void DirectSoundPlayer2Thread::Run()
DWORD bufSize = mid(min,aim,max); // size of entire playback buffer
DSBUFFERDESC desc;
desc.dwSize = sizeof(DSBUFFERDESC);
desc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
desc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
desc.dwBufferBytes = bufSize;
desc.dwReserved = 0;
desc.lpwfxFormat = &waveFormat;
@ -461,6 +462,15 @@ stop_playback:
goto do_fill_buffer;
case WAIT_OBJECT_0+3:
{
LONG invert_volume = (LONG)((this->volume - 1.0) * 5000.0); // Hrmm weirdly it's half?
// Look, I would have used a min max but it just errored out for me lol.
if (invert_volume > DSBVOLUME_MAX)
invert_volume = DSBVOLUME_MAX;
else if (invert_volume < DSBVOLUME_MIN / 2)
invert_volume = DSBVOLUME_MIN / 2;
bfr->SetVolume(invert_volume);
}
// Change volume
// We aren't thread safe right now, filling the buffers grabs volume directly
// from the field set by the controlling thread, but it shouldn't be a major
@ -608,7 +618,7 @@ DWORD DirectSoundPlayer2Thread::FillAndUnlockBuffers(void *buf1, DWORD buf1sz, v
buf2sz = 0;
}
provider->GetAudioWithVolume(buf1, input_frame, buf1szf, volume);
provider->GetAudio(buf1, input_frame, buf1szf);
input_frame += buf1szf;
}
@ -621,7 +631,7 @@ DWORD DirectSoundPlayer2Thread::FillAndUnlockBuffers(void *buf1, DWORD buf1sz, v
buf2sz = buf2szf * bytes_per_frame;
}
provider->GetAudioWithVolume(buf2, input_frame, buf2szf, volume);
provider->GetAudio(buf2, input_frame, buf2szf);
input_frame += buf2szf;
}
@ -932,4 +942,4 @@ std::unique_ptr<AudioPlayer> CreateDirectSound2Player(agi::AudioProvider *provid
return agi::make_unique<DirectSoundPlayer2>(provider, parent);
}
#endif // WITH_DIRECTSOUND
#endif // WITH_DIRECTSOUND

View File

@ -125,7 +125,7 @@ public:
OpenALPlayer::OpenALPlayer(agi::AudioProvider *provider)
: AudioPlayer(provider)
, samplerate(provider->GetSampleRate())
, bpf(provider->GetChannels() * provider->GetBytesPerSample())
, bpf(/*provider->GetChannels() * provider->GetBytesPerSample()*/sizeof(int16_t))
{
device = alcOpenDevice(nullptr);
if (!device) throw AudioPlayerOpenError("Failed opening default OpenAL device");
@ -241,7 +241,7 @@ void OpenALPlayer::FillBuffers(ALsizei count)
if (fill_len > 0)
// Get fill_len frames of audio
provider->GetAudioWithVolume(&decode_buffer[0], cur_frame, fill_len, volume);
provider->GetInt16MonoAudioWithVolume(reinterpret_cast<int16_t*>(decode_buffer.data()), cur_frame, fill_len, volume);
if ((size_t)fill_len * bpf < decode_buffer.size())
// And zerofill the rest
memset(&decode_buffer[fill_len * bpf], 0, decode_buffer.size() - fill_len * bpf);
@ -308,4 +308,4 @@ std::unique_ptr<AudioPlayer> CreateOpenALPlayer(agi::AudioProvider *provider, wx
return agi::make_unique<OpenALPlayer>(provider);
}
#endif // WITH_OPENAL
#endif // WITH_OPENAL

View File

@ -131,7 +131,7 @@ public:
while (!TestDestroy() && parent->cur_frame < parent->end_frame) {
int rsize = std::min(wsize, parent->end_frame - parent->cur_frame);
parent->provider->GetAudioWithVolume(buf, parent->cur_frame,
parent->provider->GetInt16MonoAudioWithVolume(reinterpret_cast<int16_t*>(buf), parent->cur_frame,
rsize, parent->volume);
int written = ::write(parent->dspdev, buf, rsize * parent->bpf);
parent->cur_frame += written / parent->bpf;
@ -146,7 +146,7 @@ public:
void OSSPlayer::OpenStream()
{
bpf = provider->GetChannels() * provider->GetBytesPerSample();
bpf = /*provider->GetChannels() * provider->GetBytesPerSample()*/sizeof(int16_t);
// Open device
wxString device = to_wx(OPT_GET("Player/Audio/OSS/Device")->GetString());
@ -162,14 +162,14 @@ void OSSPlayer::OpenStream()
#endif
// Set number of channels
int channels = provider->GetChannels();
int channels = /*provider->GetChannels()*/1;
if (ioctl(dspdev, SNDCTL_DSP_CHANNELS, &channels) < 0) {
throw AudioPlayerOpenError("OSS player: setting channels failed");
}
// Set sample format
int sample_format;
switch (provider->GetBytesPerSample()) {
switch (/*provider->GetBytesPerSample()*/sizeof(int16_t)) {
case 1:
sample_format = AFMT_S8;
break;
@ -283,4 +283,4 @@ std::unique_ptr<AudioPlayer> CreateOSSPlayer(agi::AudioProvider *provider, wxWin
return agi::make_unique<OSSPlayer>(provider);
}
#endif // WITH_OSS
#endif // WITH_OSS

View File

@ -140,7 +140,7 @@ void PortAudioPlayer::OpenStream() {
const PaDeviceInfo *device_info = Pa_GetDeviceInfo((*device_ids)[i]);
PaStreamParameters pa_output_p;
pa_output_p.device = (*device_ids)[i];
pa_output_p.channelCount = provider->GetChannels();
pa_output_p.channelCount = /*provider->GetChannels()*/ 1;
pa_output_p.sampleFormat = paInt16;
pa_output_p.suggestedLatency = device_info->defaultLowOutputLatency;
pa_output_p.hostApiSpecificStreamInfo = nullptr;
@ -222,7 +222,7 @@ int PortAudioPlayer::paCallback(const void *inputBuffer, void *outputBuffer,
// Play something
if (lenAvailable > 0) {
player->provider->GetAudioWithVolume(outputBuffer, player->current, lenAvailable, player->GetVolume());
player->provider->GetInt16MonoAudioWithVolume(reinterpret_cast<int16_t*>(outputBuffer), player->current, lenAvailable, player->GetVolume());
// Set play position
player->current += lenAvailable;
@ -283,4 +283,4 @@ std::unique_ptr<AudioPlayer> CreatePortAudioPlayer(agi::AudioProvider *provider,
return agi::make_unique<PortAudioPlayer>(provider);
}
#endif // WITH_PORTAUDIO
#endif // WITH_PORTAUDIO

View File

@ -133,11 +133,11 @@ PulseAudioPlayer::PulseAudioPlayer(agi::AudioProvider *provider) : AudioPlayer(p
}
// Set up stream
bpf = provider->GetChannels() * provider->GetBytesPerSample();
bpf = /*provider->GetChannels() * provider->GetBytesPerSample()*/sizeof(int16_t);
pa_sample_spec ss;
ss.format = PA_SAMPLE_S16LE; // FIXME
ss.rate = provider->GetSampleRate();
ss.channels = provider->GetChannels();
ss.channels = /*provider->GetChannels()*/1;
pa_channel_map map;
pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_DEFAULT);
@ -308,7 +308,7 @@ void PulseAudioPlayer::pa_stream_write(pa_stream *p, size_t length, PulseAudioPl
unsigned long maxframes = thread->end_frame - thread->cur_frame;
if (frames > maxframes) frames = maxframes;
void *buf = malloc(frames * bpf);
thread->provider->GetAudioWithVolume(buf, thread->cur_frame, frames, thread->volume);
thread->provider->GetInt16MonoAudioWithVolume(reinterpret_cast<int16_t*>(buf), thread->cur_frame, frames, thread->volume);
::pa_stream_write(p, buf, frames*bpf, free, 0, PA_SEEK_RELATIVE);
thread->cur_frame += frames;
}
@ -324,4 +324,4 @@ void PulseAudioPlayer::pa_stream_notify(pa_stream *p, PulseAudioPlayer *thread)
std::unique_ptr<AudioPlayer> CreatePulseAudioPlayer(agi::AudioProvider *provider, wxWindow *) {
return agi::make_unique<PulseAudioPlayer>(provider);
}
#endif // WITH_LIBPULSE
#endif // WITH_LIBPULSE

View File

@ -0,0 +1,694 @@
// Copyright (c) 2019, Qirui Wang
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Aegisub Project http://www.aegisub.org/
#ifdef WITH_XAUDIO2
#include "include/aegisub/audio_player.h"
#include "options.h"
#include <libaegisub/audio/provider.h>
#include <libaegisub/scoped_ptr.h>
#include <libaegisub/log.h>
#include <libaegisub/make_unique.h>
#ifndef XAUDIO2_REDIST
#include <xaudio2.h>
#else
#include <xaudio2redist.h>
#endif
namespace {
class XAudio2Thread;
/// @class XAudio2Player
/// @brief XAudio2-based audio player
///
/// The core design idea is to have a playback thread that performs all playback operations, and use the player object as a proxy to send commands to the playback thread.
class XAudio2Player final : public AudioPlayer {
/// The playback thread
std::unique_ptr<XAudio2Thread> thread;
/// Desired length in milliseconds to write ahead of the playback cursor
int WantedLatency;
/// Multiplier for WantedLatency to get total buffer length
int BufferLength;
/// @brief Tell whether playback thread is alive
/// @return True if there is a playback thread and it's ready
bool IsThreadAlive();
public:
/// @brief Constructor
XAudio2Player(agi::AudioProvider* provider);
/// @brief Destructor
~XAudio2Player() = default;
/// @brief Start playback
/// @param start First audio frame to play
/// @param count Number of audio frames to play
void Play(int64_t start, int64_t count);
/// @brief Stop audio playback
/// @param timerToo Whether to also stop the playback update timer
void Stop();
/// @brief Tell whether playback is active
/// @return True if audio is playing back
bool IsPlaying();
/// @brief Get playback end position
/// @return Audio frame index
///
/// Returns 0 if playback is stopped or there is no playback thread
int64_t GetEndPosition();
/// @brief Get approximate playback position
/// @return Index of audio frame user is currently hearing
///
/// Returns 0 if playback is stopped or there is no playback thread
int64_t GetCurrentPosition();
/// @brief Change playback end position
/// @param pos New end position
void SetEndPosition(int64_t pos);
/// @brief Change playback volume
/// @param vol Amplification factor
void SetVolume(double vol);
};
/// @brief RAII support class to init and de-init the COM library
struct COMInitialization {
/// Flag set if an inited COM library is managed
bool inited = false;
/// @brief Destructor, de-inits COM if it is inited
~COMInitialization() {
if (inited) CoUninitialize();
}
/// @brief Initialise the COM library as single-threaded apartment if isn't already inited by us
bool Init() {
if (!inited && SUCCEEDED(CoInitialize(nullptr)))
inited = true;
return inited;
}
};
struct ReleaseCOMObject {
void operator()(IUnknown* obj) {
if (obj) obj->Release();
}
};
/// @brief RAII wrapper around Win32 HANDLE type
struct Win32KernelHandle final : public agi::scoped_holder<HANDLE, BOOL(__stdcall*)(HANDLE)> {
/// @brief Create with a managed handle
/// @param handle Win32 handle to manage
Win32KernelHandle(HANDLE handle = 0) :scoped_holder(handle, CloseHandle) {}
Win32KernelHandle& operator=(HANDLE new_handle) {
scoped_holder::operator=(new_handle);
return *this;
}
};
/// @class XAudio2Thread
/// @brief Playback thread class for XAudio2Player
///
/// Not based on wxThread, but uses Win32 threads directly
class XAudio2Thread :public IXAudio2VoiceCallback {
/// @brief Win32 thread entry point
/// @param parameter Pointer to our thread object
/// @return Thread return value, always 0 here
static unsigned int __stdcall ThreadProc(void* parameter);
/// @brief Thread entry point
void Run();
/// @brief Check for error state and throw exception if one occurred
void CheckError();
/// Win32 handle to the thread
Win32KernelHandle thread_handle;
/// Event object, world to thread, set to start playback
Win32KernelHandle event_start_playback;
/// Event object, world to thread, set to stop playback
Win32KernelHandle event_stop_playback;
/// Event object, world to thread, set if playback end time was updated
Win32KernelHandle event_update_end_time;
/// Event object, world to thread, set if the volume was changed
Win32KernelHandle event_set_volume;
/// Event object, world to thread, set if the thread should end as soon as possible
Win32KernelHandle event_buffer_end;
/// Event object, world to thread, set if the thread should end as soon as possible
Win32KernelHandle event_kill_self;
/// Event object, thread to world, set when the thread has entered its main loop
Win32KernelHandle thread_running;
/// Event object, thread to world, set when playback is ongoing
Win32KernelHandle is_playing;
/// Event object, thread to world, set if an error state has occurred (implies thread is dying)
Win32KernelHandle error_happened;
/// Statically allocated error message text describing reason for error_happened being set
const char* error_message = nullptr;
/// Playback volume, 1.0 is "unchanged"
double volume = 1.0;
/// Audio frame to start playback at
int64_t start_frame = 0;
/// Audio frame to end playback at
int64_t end_frame = 0;
/// Desired length in milliseconds to write ahead of the playback cursor
int wanted_latency;
/// Multiplier for WantedLatency to get total buffer length
int buffer_length;
/// System millisecond timestamp of last playback start, used to calculate playback position
ULONGLONG last_playback_restart;
/// Audio provider to take sample data from
agi::AudioProvider* provider;
/// Buffer occupied indicator
std::vector<bool> buffer_occupied;
public:
/// @brief Constructor, creates and starts playback thread
/// @param provider Audio provider to take sample data from
/// @param WantedLatency Desired length in milliseconds to write ahead of the playback cursor
/// @param BufferLength Multiplier for WantedLatency to get total buffer length
XAudio2Thread(agi::AudioProvider* provider, int WantedLatency, int BufferLength);
/// @brief Destructor, waits for thread to have died
~XAudio2Thread();
// IXAudio2VoiceCallback
void STDMETHODCALLTYPE OnVoiceProcessingPassStart(UINT32 BytesRequired) override {}
void STDMETHODCALLTYPE OnVoiceProcessingPassEnd() override {}
void STDMETHODCALLTYPE OnStreamEnd() override {}
void STDMETHODCALLTYPE OnBufferStart(void* pBufferContext) override {}
void STDMETHODCALLTYPE OnBufferEnd(void* pBufferContext) override {
intptr_t i = reinterpret_cast<intptr_t>(pBufferContext);
buffer_occupied[i] = false;
SetEvent(event_buffer_end);
}
void STDMETHODCALLTYPE OnLoopEnd(void* pBufferContext) override {}
void STDMETHODCALLTYPE OnVoiceError(void* pBufferContext, HRESULT Error) override {}
/// @brief Start audio playback
/// @param start Audio frame to start playback at
/// @param count Number of audio frames to play
void Play(int64_t start, int64_t count);
/// @brief Stop audio playback
void Stop();
/// @brief Change audio playback end point
/// @param new_end_frame New last audio frame to play
///
/// Playback stops instantly if new_end_frame is before the current playback position
void SetEndFrame(int64_t new_end_frame);
/// @brief Change audio playback volume
/// @param new_volume New playback amplification factor, 1.0 is "unchanged"
void SetVolume(double new_volume);
/// @brief Tell whether audio playback is active
/// @return True if audio is being played back, false if it is not
bool IsPlaying();
/// @brief Get approximate current audio frame being heard by the user
/// @return Audio frame index
///
/// Returns 0 if not playing
int64_t GetCurrentFrame();
/// @brief Get audio playback end point
/// @return Audio frame index
int64_t GetEndFrame();
/// @brief Tell whether playback thread has died
/// @return True if thread is no longer running
bool IsDead();
};
unsigned int __stdcall XAudio2Thread::ThreadProc(void* parameter) {
static_cast<XAudio2Thread*>(parameter)->Run();
return 0;
}
/// Macro used to set error_message, error_happened and end the thread
#define REPORT_ERROR(msg) \
{ \
ResetEvent(is_playing); \
error_message = "XAudio2Thread: " msg; \
SetEvent(error_happened); \
return; \
}
void XAudio2Thread::Run() {
COMInitialization COM_library;
if (!COM_library.Init()) {
REPORT_ERROR("Could not initialise COM")
}
IXAudio2* pXAudio2;
IXAudio2SourceVoice* pSourceVoice;
HRESULT hr;
if (FAILED(hr = XAudio2Create(&pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR))) {
REPORT_ERROR("Failed initializing XAudio2")
}
IXAudio2MasteringVoice* pMasterVoice = NULL;
if (FAILED(hr = pXAudio2->CreateMasteringVoice(&pMasterVoice))) {
REPORT_ERROR("Failed initializing XAudio2 MasteringVoice")
}
// Describe the wave format
WAVEFORMATEX wfx;
wfx.nSamplesPerSec = provider->GetSampleRate();
wfx.cbSize = 0;
bool original = true;
wfx.wFormatTag = provider->AreSamplesFloat() ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
wfx.nChannels = provider->GetChannels();
wfx.wBitsPerSample = provider->GetBytesPerSample() * 8;
wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8;
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
if (FAILED(hr = pXAudio2->CreateSourceVoice(&pSourceVoice, &wfx, 0, 2, this))) {
if (hr == XAUDIO2_E_INVALID_CALL) {
// Retry with 16bit mono
original = false;
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = 1;
wfx.wBitsPerSample = sizeof(int16_t) * 8;
wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8;
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
if (FAILED(hr = pXAudio2->CreateSourceVoice(&pSourceVoice, &wfx, 0, 2, this))) {
REPORT_ERROR("Failed initializing XAudio2 SourceVoice")
}
}
else {
REPORT_ERROR("Failed initializing XAudio2 SourceVoice")
}
}
// Now we're ready to roll!
SetEvent(thread_running);
bool running = true;
HANDLE events_to_wait[] = {
event_start_playback,
event_stop_playback,
event_update_end_time,
event_set_volume,
event_buffer_end,
event_kill_self
};
int64_t next_input_frame = 0;
DWORD buffer_offset = 0;
bool playback_should_be_running = false;
int current_latency = wanted_latency;
const int wanted_frames = wanted_latency * wfx.nSamplesPerSec / 1000;
const DWORD wanted_latency_bytes = wanted_frames * wfx.nBlockAlign;
std::vector<std::vector<BYTE> > buff(buffer_length);
for (auto& i : buff)
i.resize(wanted_latency_bytes);
while (running) {
DWORD wait_result = WaitForMultipleObjects(sizeof(events_to_wait) / sizeof(HANDLE), events_to_wait, FALSE, INFINITE);
switch (wait_result) {
case WAIT_OBJECT_0 + 0:
// Start or restart playback
pSourceVoice->Stop();
pSourceVoice->FlushSourceBuffers();
next_input_frame = start_frame;
playback_should_be_running = true;
pSourceVoice->Start();
SetEvent(is_playing);
goto do_fill_buffer;
case WAIT_OBJECT_0 + 1:
stop_playback:
// Stop playing
ResetEvent(is_playing);
pSourceVoice->Stop();
pSourceVoice->FlushSourceBuffers();
playback_should_be_running = false;
break;
case WAIT_OBJECT_0 + 2:
// Set end frame
if (end_frame <= next_input_frame)
goto stop_playback;
goto do_fill_buffer;
case WAIT_OBJECT_0 + 3:
// Change volume
pSourceVoice->SetVolume(volume);
break;
case WAIT_OBJECT_0 + 4:
// Buffer end
do_fill_buffer:
// Time to fill more into buffer
if (!playback_should_be_running)
break;
for (int i = 0; i < buffer_length; ++i) {
if (!buffer_occupied[i]) {
int fill_len = std::min<int>(end_frame - next_input_frame, wanted_frames);
if (fill_len <= 0)
break;
buffer_occupied[i] = true;
if (original)
provider->GetAudio(buff[i].data(), next_input_frame, fill_len);
else
provider->GetInt16MonoAudio(reinterpret_cast<int16_t*>(buff[i].data()), next_input_frame, fill_len);
next_input_frame += fill_len;
XAUDIO2_BUFFER xbf;
xbf.Flags = fill_len + next_input_frame == end_frame ? XAUDIO2_END_OF_STREAM : 0;
xbf.AudioBytes = fill_len * wfx.nBlockAlign;
xbf.pAudioData = buff[i].data();
xbf.PlayBegin = 0;
xbf.PlayLength = 0;
xbf.LoopBegin = 0;
xbf.LoopLength = 0;
xbf.LoopCount = 0;
xbf.pContext = reinterpret_cast<void*>(static_cast<intptr_t>(i));
if (FAILED(hr = pSourceVoice->SubmitSourceBuffer(&xbf))) {
REPORT_ERROR("Failed initializing Submit Buffer")
}
}
}
break;
case WAIT_OBJECT_0 + 5:
// Perform suicide
running = false;
pXAudio2->Release();
ResetEvent(is_playing);
playback_should_be_running = false;
break;
default:
REPORT_ERROR("Something bad happened while waiting on events in playback loop, either the wait failed or an event object was abandoned.")
break;
}
}
}
#undef REPORT_ERROR
void XAudio2Thread::CheckError()
{
try {
switch (WaitForSingleObject(error_happened, 0))
{
case WAIT_OBJECT_0:
throw error_message;
case WAIT_ABANDONED:
throw "The XAudio2Thread error signal event was abandoned, somehow. This should not happen.";
case WAIT_FAILED:
throw "Failed checking state of XAudio2Thread error signal event.";
case WAIT_TIMEOUT:
default:
return;
}
}
catch (...) {
ResetEvent(is_playing);
ResetEvent(thread_running);
throw;
}
}
XAudio2Thread::XAudio2Thread(agi::AudioProvider* provider, int WantedLatency, int BufferLength)
: event_start_playback(CreateEvent(0, FALSE, FALSE, 0))
, event_stop_playback(CreateEvent(0, FALSE, FALSE, 0))
, event_update_end_time(CreateEvent(0, FALSE, FALSE, 0))
, event_set_volume(CreateEvent(0, FALSE, FALSE, 0))
, event_buffer_end(CreateEvent(0, FALSE, FALSE, 0))
, event_kill_self(CreateEvent(0, FALSE, FALSE, 0))
, thread_running(CreateEvent(0, TRUE, FALSE, 0))
, is_playing(CreateEvent(0, TRUE, FALSE, 0))
, error_happened(CreateEvent(0, FALSE, FALSE, 0))
, wanted_latency(WantedLatency)
, buffer_length(BufferLength < XAUDIO2_MAX_QUEUED_BUFFERS ? BufferLength : XAUDIO2_MAX_QUEUED_BUFFERS)
, provider(provider)
, buffer_occupied(BufferLength)
{
if (!(thread_handle = (HANDLE)_beginthreadex(0, 0, ThreadProc, this, 0, 0))) {
throw AudioPlayerOpenError("Failed creating playback thread in XAudio2Player. This is bad.");
}
HANDLE running_or_error[] = { thread_running, error_happened };
switch (WaitForMultipleObjects(2, running_or_error, FALSE, INFINITE)) {
case WAIT_OBJECT_0:
// running, all good
return;
case WAIT_OBJECT_0 + 1:
// error happened, we fail
throw AudioPlayerOpenError(error_message ? error_message : "Failed wait for thread start or thread error in XAudio2Player. This is bad.");
default:
throw AudioPlayerOpenError("Failed wait for thread start or thread error in XAudio2Player. This is bad.");
}
}
XAudio2Thread::~XAudio2Thread() {
SetEvent(event_kill_self);
WaitForSingleObject(thread_handle, INFINITE);
}
void XAudio2Thread::Play(int64_t start, int64_t count)
{
CheckError();
start_frame = start;
end_frame = start + count;
SetEvent(event_start_playback);
last_playback_restart = GetTickCount64();
// Block until playback actually begins to avoid race conditions with
// checking if playback is in progress
HANDLE events_to_wait[] = { is_playing, error_happened };
switch (WaitForMultipleObjects(2, events_to_wait, FALSE, INFINITE)) {
case WAIT_OBJECT_0 + 0: // Playing
LOG_D("audio/player/xaudio2") << "Playback begun";
break;
case WAIT_OBJECT_0 + 1: // Error
throw error_message;
default:
throw agi::InternalError("Unexpected result from WaitForMultipleObjects in XAudio2Thread::Play");
}
}
void XAudio2Thread::Stop() {
CheckError();
SetEvent(event_stop_playback);
}
void XAudio2Thread::SetEndFrame(int64_t new_end_frame) {
CheckError();
end_frame = new_end_frame;
SetEvent(event_update_end_time);
}
void XAudio2Thread::SetVolume(double new_volume) {
CheckError();
volume = new_volume;
SetEvent(event_set_volume);
}
bool XAudio2Thread::IsPlaying() {
CheckError();
switch (WaitForSingleObject(is_playing, 0))
{
case WAIT_ABANDONED:
throw "The XAudio2Thread playback state event was abandoned, somehow. This should not happen.";
case WAIT_FAILED:
throw "Failed checking state of XAudio2Thread playback state event.";
case WAIT_OBJECT_0:
return true;
case WAIT_TIMEOUT:
default:
return false;
}
}
int64_t XAudio2Thread::GetCurrentFrame() {
CheckError();
if (!IsPlaying()) return 0;
ULONGLONG milliseconds_elapsed = GetTickCount64() - last_playback_restart;
return start_frame + milliseconds_elapsed * provider->GetSampleRate() / 1000;
}
int64_t XAudio2Thread::GetEndFrame() {
CheckError();
return end_frame;
}
bool XAudio2Thread::IsDead() {
switch (WaitForSingleObject(thread_running, 0))
{
case WAIT_OBJECT_0:
return false;
default:
return true;
}
}
XAudio2Player::XAudio2Player(agi::AudioProvider* provider) :AudioPlayer(provider) {
// The buffer will hold BufferLength times WantedLatency milliseconds of audio
WantedLatency = OPT_GET("Player/Audio/DirectSound/Buffer Latency")->GetInt();
BufferLength = OPT_GET("Player/Audio/DirectSound/Buffer Length")->GetInt();
// sanity checking
if (WantedLatency <= 0)
WantedLatency = 100;
if (BufferLength <= 0)
BufferLength = 5;
try {
thread = agi::make_unique<XAudio2Thread>(provider, WantedLatency, BufferLength);
}
catch (const char* msg) {
LOG_E("audio/player/xaudio2") << msg;
throw AudioPlayerOpenError(msg);
}
}
bool XAudio2Player::IsThreadAlive() {
if (thread && thread->IsDead())
thread.reset();
return static_cast<bool>(thread);
}
void XAudio2Player::Play(int64_t start, int64_t count) {
try {
thread->Play(start, count);
}
catch (const char* msg) {
LOG_E("audio/player/xaudio2") << msg;
}
}
void XAudio2Player::Stop() {
try {
if (IsThreadAlive()) thread->Stop();
}
catch (const char* msg) {
LOG_E("audio/player/xaudio2") << msg;
}
}
bool XAudio2Player::IsPlaying() {
try {
if (!IsThreadAlive()) return false;
return thread->IsPlaying();
}
catch (const char* msg) {
LOG_E("audio/player/xaudio2") << msg;
return false;
}
}
int64_t XAudio2Player::GetEndPosition() {
try {
if (!IsThreadAlive()) return 0;
return thread->GetEndFrame();
}
catch (const char* msg) {
LOG_E("audio/player/xaudio2") << msg;
return 0;
}
}
int64_t XAudio2Player::GetCurrentPosition() {
try {
if (!IsThreadAlive()) return 0;
return thread->GetCurrentFrame();
}
catch (const char* msg) {
LOG_E("audio/player/xaudio2") << msg;
return 0;
}
}
void XAudio2Player::SetEndPosition(int64_t pos) {
try {
if (IsThreadAlive()) thread->SetEndFrame(pos);
}
catch (const char* msg) {
LOG_E("audio/player/xaudio2") << msg;
}
}
void XAudio2Player::SetVolume(double vol) {
try {
if (IsThreadAlive()) thread->SetVolume(vol);
}
catch (const char* msg) {
LOG_E("audio/player/xaudio2") << msg;
}
}
}
std::unique_ptr<AudioPlayer> CreateXAudio2Player(agi::AudioProvider* provider, wxWindow*) {
return agi::make_unique<XAudio2Player>(provider);
}
#endif // WITH_XAUDIO2

View File

@ -0,0 +1,89 @@
// Copyright (c) 2022, arch1t3cht <arch1t3cht@gmail.com>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
/// @file audio_provider_bestsource.cpp
/// @brief BS-based audio provider
/// @ingroup audio_input bestsource
///
#ifdef WITH_BESTSOURCE
#include <libaegisub/audio/provider.h>
#include "audiosource.h"
#include "bestsource_common.h"
#include "compat.h"
#include "options.h"
#include <libaegisub/fs.h>
#include <libaegisub/make_unique.h>
#include <libaegisub/background_runner.h>
#include <libaegisub/log.h>
#include <map>
namespace {
class BSAudioProvider final : public agi::AudioProvider {
std::map<std::string, std::string> bsopts;
BestAudioSource bs;
AudioProperties properties;
void FillBuffer(void *Buf, int64_t Start, int64_t Count) const override;
public:
BSAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *br);
bool NeedsCache() const override { return OPT_GET("Provider/Audio/BestSource/Aegisub Cache")->GetBool(); }
};
/// @brief Constructor
/// @param filename The filename to open
BSAudioProvider::BSAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *br) try
: bsopts()
, bs(filename.string(), -1, -1, GetBSCacheFile(filename), &bsopts)
{
bs.SetMaxCacheSize(OPT_GET("Provider/Audio/BestSource/Max Cache Size")->GetInt() << 20);
br->Run([&](agi::ProgressSink *ps) {
ps->SetTitle(from_wx(_("Exacting")));
ps->SetMessage(from_wx(_("Creating cache... This can take a while!")));
ps->SetIndeterminate();
if (bs.GetExactDuration()) {
LOG_D("bs") << "File cached and has exact samples.";
}
});
properties = bs.GetAudioProperties();
float_samples = properties.IsFloat;
bytes_per_sample = properties.BytesPerSample;
sample_rate = properties.SampleRate;
channels = properties.Channels;
num_samples = properties.NumSamples;
decoded_samples = OPT_GET("Provider/Audio/BestSource/Aegisub Cache")->GetBool() ? 0 : num_samples;
}
catch (AudioException const& err) {
throw agi::AudioProviderError("Failed to create BestAudioSource");
}
void BSAudioProvider::FillBuffer(void *Buf, int64_t Start, int64_t Count) const {
const_cast<BestAudioSource &>(bs).GetPackedAudio(reinterpret_cast<uint8_t *>(Buf), Start, Count);
}
}
std::unique_ptr<agi::AudioProvider> CreateBSAudioProvider(agi::fs::path const& file, agi::BackgroundRunner *br) {
return agi::make_unique<BSAudioProvider>(file, br);
}
#endif /* WITH_BESTSOURCE */

View File

@ -31,6 +31,8 @@ using namespace agi;
std::unique_ptr<AudioProvider> CreateAvisynthAudioProvider(fs::path const& filename, BackgroundRunner *);
std::unique_ptr<AudioProvider> CreateFFmpegSourceAudioProvider(fs::path const& filename, BackgroundRunner *);
std::unique_ptr<AudioProvider> CreateBSAudioProvider(fs::path const& filename, BackgroundRunner *);
std::unique_ptr<AudioProvider> CreateVapoursynthAudioProvider(fs::path const& filename, BackgroundRunner *);
namespace {
struct factory {
@ -48,6 +50,12 @@ const factory providers[] = {
#ifdef WITH_AVISYNTH
{"Avisynth", CreateAvisynthAudioProvider, false},
#endif
#ifdef WITH_BESTSOURCE
{"BestSource", CreateBSAudioProvider, false},
#endif
#ifdef WITH_VAPOURSYNTH
{"Vapoursynth", CreateVapoursynthAudioProvider, false},
#endif
};
}

167
src/audio_provider_vs.cpp Normal file
View File

@ -0,0 +1,167 @@
// Copyright (c) 2022, arch1t3cht <arch1t3cht@gmail.com>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
/// @file audio_provider_vs.cpp
/// @brief Vapoursynth-based audio provider
/// @ingroup audio_input
///
#ifdef WITH_VAPOURSYNTH
#include <libaegisub/audio/provider.h>
#include "audio_controller.h"
#include "options.h"
#include "utils.h"
#include <libaegisub/access.h>
#include <libaegisub/format.h>
#include <libaegisub/fs.h>
#include <libaegisub/path.h>
#include <libaegisub/make_unique.h>
#include <mutex>
#include "vapoursynth_wrap.h"
#include "VSScript4.h"
namespace {
class VapoursynthAudioProvider final : public agi::AudioProvider {
VapourSynthWrapper vs;
VSScript *script = nullptr;
VSNode *node = nullptr;
const VSAudioInfo *vi = nullptr;
void FillBufferWithFrame(void *buf, int frame, int64_t start, int64_t count) const;
void FillBuffer(void *buf, int64_t start, int64_t count) const override;
public:
VapoursynthAudioProvider(agi::fs::path const& filename);
~VapoursynthAudioProvider();
bool NeedsCache() const override { return true; }
};
VapoursynthAudioProvider::VapoursynthAudioProvider(agi::fs::path const& filename) try {
agi::acs::CheckFileRead(filename);
std::lock_guard<std::mutex> lock(vs.GetMutex());
script = vs.GetScriptAPI()->createScript(nullptr);
if (script == nullptr) {
throw VapoursynthError("Error creating script API");
}
vs.GetScriptAPI()->evalSetWorkingDir(script, 1);
if (vs.GetScriptAPI()->evaluateFile(script, filename.string().c_str())) {
std::string msg = agi::format("Error executing VapourSynth script: %s", vs.GetScriptAPI()->getError(script));
vs.GetScriptAPI()->freeScript(script);
throw VapoursynthError(msg);
}
node = vs.GetScriptAPI()->getOutputNode(script, 0);
if (node == nullptr) {
vs.GetScriptAPI()->freeScript(script);
throw VapoursynthError("No output node set");
}
if (vs.GetAPI()->getNodeType(node) != mtAudio) {
vs.GetAPI()->freeNode(node);
vs.GetScriptAPI()->freeScript(script);
throw VapoursynthError("Output node isn't an audio node");
}
vi = vs.GetAPI()->getAudioInfo(node);
float_samples = vi->format.sampleType == stFloat;
bytes_per_sample = vi->format.bytesPerSample;
sample_rate = vi->sampleRate;
channels = vi->format.numChannels;
num_samples = vi->numSamples;
}
catch (VapoursynthError const& err) {
throw agi::AudioProviderError(agi::format("Vapoursynth error: %s", err.GetMessage()));
}
template<typename T>
static void PackChannels(const uint8_t **Src, void *Dst, size_t Length, size_t Channels) {
T *D = reinterpret_cast<T *>(Dst);
for (size_t c = 0; c < Channels; c++) {
const T *S = reinterpret_cast<const T *>(Src[c]);
for (size_t i = 0; i < Length; i++) {
D[Channels * i + c] = S[i];
}
}
}
void VapoursynthAudioProvider::FillBufferWithFrame(void *buf, int n, int64_t start, int64_t count) const {
char errorMsg[1024];
const VSFrame *frame = vs.GetAPI()->getFrame(n, node, errorMsg, sizeof(errorMsg));
if (frame == nullptr) {
throw VapoursynthError(agi::format("Error getting frame: %s", errorMsg));
}
if (vs.GetAPI()->getFrameLength(frame) < count) {
vs.GetAPI()->freeFrame(frame);
throw VapoursynthError("Audio frame too short");
}
if (vs.GetAPI()->getAudioFrameFormat(frame)->numChannels != channels || vs.GetAPI()->getAudioFrameFormat(frame)->bytesPerSample != bytes_per_sample) {
vs.GetAPI()->freeFrame(frame);
throw VapoursynthError("Audio format is not constant");
}
std::vector<const uint8_t *> planes(channels);
for (int c = 0; c < channels; c++) {
planes[c] = vs.GetAPI()->getReadPtr(frame, c);
if (planes[c] == nullptr) {
vs.GetAPI()->freeFrame(frame);
throw VapoursynthError("Failed to read audio channel");
}
}
if (bytes_per_sample == 1)
PackChannels<uint8_t>(planes.data(), buf, count, channels);
else if (bytes_per_sample == 2)
PackChannels<uint16_t>(planes.data(), buf, count, channels);
else if (bytes_per_sample == 4)
PackChannels<uint32_t>(planes.data(), buf, count, channels);
else if (bytes_per_sample == 8)
PackChannels<uint64_t>(planes.data(), buf, count, channels);
vs.GetAPI()->freeFrame(frame);
}
void VapoursynthAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const {
int end = start + count; // exclusive
int startframe = start / VS_AUDIO_FRAME_SAMPLES;
int endframe = (end - 1) / VS_AUDIO_FRAME_SAMPLES;
int offset = start - (VS_AUDIO_FRAME_SAMPLES * startframe);
for (int frame = startframe; frame <= endframe; frame++) {
int framestart = frame * VS_AUDIO_FRAME_SAMPLES;
int frameend = (frame + 1) * VS_AUDIO_FRAME_SAMPLES;
int fstart = framestart < start ? start - framestart : 0;
int fcount = VS_AUDIO_FRAME_SAMPLES - fstart - (frameend > end ? frameend - end : 0);
int bufstart = frame == startframe ? 0 : (frame - startframe) * VS_AUDIO_FRAME_SAMPLES - offset;
FillBufferWithFrame(reinterpret_cast<uint8_t *>(buf) + channels * bytes_per_sample * bufstart, frame, fstart, fcount);
}
}
VapoursynthAudioProvider::~VapoursynthAudioProvider() {
if (node != nullptr) {
vs.GetAPI()->freeNode(node);
}
if (script != nullptr) {
vs.GetScriptAPI()->freeScript(script);
}
}
}
std::unique_ptr<agi::AudioProvider> CreateVapoursynthAudioProvider(agi::fs::path const& file, agi::BackgroundRunner *) {
return agi::make_unique<VapoursynthAudioProvider>(file);
}
#endif

View File

@ -208,8 +208,8 @@ void AudioSpectrumRenderer::FillBlock(size_t block_index, float *block)
assert(block);
int64_t first_sample = (((int64_t)block_index) << derivation_dist) - ((int64_t)1 << derivation_size);
provider->GetAudio(&audio_scratch[0], first_sample, 2 << derivation_size);
provider->GetInt16MonoAudio(audio_scratch.data(), first_sample, 2 << derivation_size);
// Because the FFTs used here are unnormalized DFTs, we have to compensate
// the possible length difference between derivation_size used in the
// calculations and its user-provided counterpart. Thus, the display is

View File

@ -88,7 +88,7 @@ void AudioWaveformRenderer::Render(wxBitmap &bmp, int start, AudioRenderingStyle
for (int x = 0; x < rect.width; ++x)
{
provider->GetAudio(audio_buffer.get(), (int64_t)cur_sample, (int64_t)pixel_samples);
provider->GetInt16MonoAudio(reinterpret_cast<int16_t*>(audio_buffer.get()), (int64_t)cur_sample, (int64_t)pixel_samples);
cur_sample += pixel_samples;
int peak_min = 0, peak_max = 0;

View File

@ -284,6 +284,10 @@ namespace Automation4 {
max = DBL_MAX;
min = -DBL_MAX;
}
if (step != 0.0) {
min = min == -DBL_MAX ? 0.0 : min;
max = max == DBL_MAX ? 100.0 : max;
}
}
bool CanSerialiseValue() const override { return true; }

49
src/bestsource_common.cpp Normal file
View File

@ -0,0 +1,49 @@
// Copyright (c) 2022, arch1t3cht <arch1t3cht@gmail.com>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
/// @file ffmpegsource_common.cpp
/// @brief Shared code for ffms video and audio providers
/// @ingroup video_input audio_input ffms
///
#ifdef WITH_BESTSOURCE
#include "bestsource_common.h"
#include "options.h"
#include <libaegisub/fs.h>
#include <libaegisub/path.h>
#include <boost/crc.hpp>
#include <boost/filesystem/path.hpp>
std::string GetBSCacheFile(agi::fs::path const& filename) {
// BS can store all its index data in a single file, but we make a separate index file
// for each video file to ensure that the old index is invalidated if the file is modified.
// While BS does check the filesize of the files, it doesn't check the modification time.
uintmax_t len = agi::fs::Size(filename);
boost::crc_32_type hash;
hash.process_bytes(filename.string().c_str(), filename.string().size());
auto result = config::path->Decode("?local/bsindex/" + filename.filename().string() + "_" + std::to_string(hash.checksum()) + "_" + std::to_string(len) + "_" + std::to_string(agi::fs::ModifiedTime(filename)) + ".json");
agi::fs::CreateDirectory(result.parent_path());
return result.string();
}
#endif // WITH_BESTSOURCE

28
src/bestsource_common.h Normal file
View File

@ -0,0 +1,28 @@
// Copyright (c) 2022, arch1t3cht <arch1t3cht@gmail.com>>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
/// @file ffmpegsource_common.h
/// @see ffmpegsource_common.cpp
/// @ingroup video_input audio_input ffms
///
#ifdef WITH_BESTSOURCE
#include <libaegisub/fs_fwd.h>
std::string GetBSCacheFile(agi::fs::path const& filename);
#endif /* WITH_BESTSOURCE */

View File

@ -80,7 +80,7 @@ struct audio_open final : public Command {
STR_HELP("Open an audio file")
void operator()(agi::Context *c) override {
auto str = from_wx(_("Audio Formats") + " (*.aac,*.ac3,*.ape,*.dts,*.eac3,*.flac,*.m4a,*.mka,*.mp3,*.mp4,*.ogg,*.opus,*.w64,*.wav,*.wma)|*.aac;*.ac3;*.ape;*.dts;*.eac3;*.flac;*.m4a;*.mka;*.mp3;*.mp4;*.ogg;*.opus;*.w64;*.wav;*.wma|"
auto str = from_wx(_("Audio Formats") + " (*.aac,*.ac3,*.ape,*.avs,*.dts,*.eac3,*.flac,*.m4a,*.mka,*.mp3,*.mp4,*.ogg,*.opus,*.vpy,*.w64,*.wav,*.wma)|*.aac;*.ac3;*.ape;*.avs;*.dts;*.eac3;*.flac;*.m4a;*.mka;*.mp3;*.mp4;*.ogg;*.opus;*.vpy;*.w64;*.wav;*.wma|"
+ _("Video Formats") + " (*.asf,*.avi,*.avs,*.d2v,*.m2ts,*.m4v,*.mkv,*.mov,*.mp4,*.mpeg,*.mpg,*.ogm,*.webm,*.wmv,*.ts)|*.asf;*.avi;*.avs;*.d2v;*.m2ts;*.m4v;*.mkv;*.mov;*.mp4;*.mpeg;*.mpg;*.ogm;*.webm;*.wmv;*.ts|"
+ _("All Files") + " (*.*)|*.*");
auto filename = OpenFileSelector(_("Open Audio File"), "Path/Last/Audio", "", "", str, c->parent);

View File

@ -581,7 +581,7 @@ struct video_open final : public Command {
STR_HELP("Open a video file")
void operator()(agi::Context *c) override {
auto str = from_wx(_("Video Formats") + " (*.asf,*.avi,*.avs,*.d2v,*.h264,*.hevc,*.m2ts,*.m4v,*.mkv,*.mov,*.mp4,*.mpeg,*.mpg,*.ogm,*.webm,*.wmv,*.ts,*.y4m,*.yuv)|*.asf;*.avi;*.avs;*.d2v;*.h264;*.hevc;*.m2ts;*.m4v;*.mkv;*.mov;*.mp4;*.mpeg;*.mpg;*.ogm;*.webm;*.wmv;*.ts;*.y4m;*.yuv|"
auto str = from_wx(_("Video Formats") + " (*.asf,*.avi,*.avs,*.d2v,*.h264,*.hevc,*.m2ts,*.m4v,*.mkv,*.mov,*.mp4,*.mpeg,*.mpg,*.ogm,*.webm,*.wmv,*.ts,*.vpy,*.y4m,*.yuv)|*.asf;*.avi;*.avs;*.d2v;*.h264;*.hevc;*.m2ts;*.m4v;*.mkv;*.mov;*.mp4;*.mpeg;*.mpg;*.ogm;*.webm;*.wmv;*.ts;*.vpy;*.y4m;*.yuv|"
+ _("All Files") + " (*.*)|*.*");
auto filename = OpenFileSelector(_("Open video file"), "Path/Last/Video", "", "", str, c->parent);
if (!filename.empty())

View File

@ -388,7 +388,7 @@ void ColorPickerScreenDropper::DropFromScreenXY(int x, int y) {
#ifndef __WXMAC__
std::unique_ptr<wxDC> screen;
if (!OPT_GET("Tool/Color Picker/Restrict to Window")->GetBool()) {
if (!OPT_GET("Tool/Colour Picker/Restrict to Window")->GetBool()) {
screen = agi::make_unique<wxScreenDC>();
} else {
wxWindow *superparent = GetParent();
@ -397,7 +397,7 @@ void ColorPickerScreenDropper::DropFromScreenXY(int x, int y) {
}
superparent->ScreenToClient(&x, &y);
screen = agi::make_unique<wxWindowDC>(superparent);
screen = agi::make_unique<wxClientDC>(superparent);
}
capdc.StretchBlit(0, 0, resx * magnification, resy * magnification,
screen.get(), x - resx / 2, y - resy / 2, resx, resy);

View File

@ -336,6 +336,10 @@
"FFmpegSource" : {
"Decode Error Handling" : "ignore",
"Downmix" : false
},
"BestSource": {
"Max Cache Size" : 100,
"Aegisub Cache" : true
}
},
"Avisynth" : {
@ -356,6 +360,11 @@
"FFmpegSource" : {
"Decoding Threads" : -1,
"Unsafe Seeking" : false
},
"BestSource" : {
"Max Cache Size" : 1024,
"Threads" : 0,
"Seek Preroll" : 12
}
}
},
@ -451,6 +460,7 @@
"X" : -1,
"Y" : -1
},
"Restrict to Window" : false,
"Maximized" : false
},
"Fonts Collector" : {
@ -576,9 +586,6 @@
"Maximized" : false,
"Skip Whitespace" : true
},
"Color Picker" : {
"Restrict to Window" : false
},
"Visual" : {
"Autohide": false
},

View File

@ -334,7 +334,12 @@
"Sample Rate" : 0
},
"FFmpegSource" : {
"Decode Error Handling" : "ignore"
"Decode Error Handling" : "ignore",
"Downmix" : false
},
"BestSource": {
"Max Cache Size" : 100,
"Aegisub Cache" : true
}
},
"Avisynth" : {
@ -355,6 +360,11 @@
"FFmpegSource" : {
"Decoding Threads" : -1,
"Unsafe Seeking" : false
},
"BestSource" : {
"Max Cache Size" : 1024,
"Threads" : 0,
"Seek Preroll" : 12
}
}
},
@ -450,6 +460,7 @@
"X" : -1,
"Y" : -1
},
"Restrict to Window" : false,
"Maximized" : false
},
"Fonts Collector" : {
@ -575,9 +586,6 @@
"Maximized" : false,
"Skip Whitespace" : true
},
"Color Picker" : {
"Restrict to Window" : false
},
"Visual" : {
"Autohide": false
},

View File

@ -233,10 +233,16 @@ opt_src = [
['OSS', 'audio_player_oss.cpp'],
['DirectSound', ['audio_player_dsound.cpp',
'audio_player_dsound2.cpp']],
['XAudio2', 'audio_player_xaudio2.cpp'],
['FFMS2', ['audio_provider_ffmpegsource.cpp',
'video_provider_ffmpegsource.cpp',
'ffmpegsource_common.cpp']],
['BestSource', ['audio_provider_bestsource.cpp',
'video_provider_bestsource.cpp',
'bestsource_common.cpp']],
['VapourSynth', ['vapoursynth_wrap.cpp',
'audio_provider_vs.cpp',
'video_provider_vs.cpp']],
['AviSynth', ['avisynth_wrap.cpp',
'audio_provider_avs.cpp',

View File

@ -231,8 +231,8 @@ void Interface(wxTreebook *book, Preferences *parent) {
auto tl_assistant = p->PageSizer(_("Translation Assistant"));
p->OptionAdd(tl_assistant, _("Skip over whitespace"), "Tool/Translation Assistant/Skip Whitespace");
auto color_picker = p->PageSizer(_("Color Picker"));
p->OptionAdd(color_picker, _("Restrict Screen Picker to Window"), "Tool/Color Picker/Restrict to Window");
auto color_picker = p->PageSizer(_("Colour Picker"));
p->OptionAdd(color_picker, _("Restrict Screen Picker to Window"), "Tool/Colour Picker/Restrict to Window");
p->SetSizerAndFit(p->sizer);
}
@ -417,6 +417,13 @@ void Advanced_Audio(wxTreebook *book, Preferences *parent) {
stereo->SetToolTip("Reduces memory usage on surround audio, but may cause audio tracks to sound blank in specific circumstances. This will not affect audio with two channels or less.");
#endif
#ifdef WITH_BESTSOURCE
auto bs = p->PageSizer("BestSource");
p->OptionAdd(bs, _("Max BS cache size (MB)"), "Provider/Audio/BestSource/Max Cache Size");
p->OptionAdd(bs, _("Use Aegisub's Cache"), "Provider/Audio/BestSource/Aegisub Cache");
#endif
#ifdef WITH_PORTAUDIO
auto portaudio = p->PageSizer("Portaudio");
p->OptionChoice(portaudio, _("Portaudio device"), PortAudioPlayer::GetOutputDevices(), "Player/Audio/PortAudio/Device Name");
@ -469,6 +476,13 @@ void Advanced_Video(wxTreebook *book, Preferences *parent) {
p->OptionAdd(ffms, _("Enable unsafe seeking"), "Provider/Video/FFmpegSource/Unsafe Seeking");
#endif
#ifdef WITH_BESTSOURCE
auto bs = p->PageSizer("BestSource");
p->OptionAdd(bs, _("Max cache size (MB)"), "Provider/Video/BestSource/Max Cache Size");
p->OptionAdd(bs, _("Decoder Threads (0 to autodetect)"), "Provider/Video/BestSource/Threads");
p->OptionAdd(bs, _("Seek preroll (Frames)"), "Provider/Video/BestSource/Seek Preroll");
#endif
p->SetSizerAndFit(p->sizer);
}

View File

@ -39,8 +39,13 @@ eyedropper_cursor CURSOR "../bitmaps/windows/eyedropper.cur"
#endif
VS_VERSION_INFO VERSIONINFO
#ifdef TAGGED_RELEASE
FILEVERSION RESOURCE_BASE_VERSION, BUILD_GIT_VERSION_NUMBER
PRODUCTVERSION RESOURCE_BASE_VERSION, 0
#else
FILEVERSION BUILD_GIT_VERSION_NUMBER, BUILD_GIT_VERSION_NUMBER
PRODUCTVERSION BUILD_GIT_VERSION_NUMBER, 0
#endif
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
FILEFLAGS (AGI_RC_FLAG_DEBUG|AGI_RC_FLAG_PRERELEASE)
FILEOS VOS__WINDOWS32

105
src/vapoursynth_wrap.cpp Normal file
View File

@ -0,0 +1,105 @@
// Copyright (c) 2022, arch1t3cht <arch1t3cht@gmail.com>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
/// @file vapoursynth_wrap.cpp
/// @brief Wrapper-layer for Vapoursynth
/// @ingroup video_input audio_input
///
#ifdef WITH_VAPOURSYNTH
#include "vapoursynth_wrap.h"
#include "VSScript4.h"
#include "options.h"
#include <mutex>
#ifndef _WIN32
#include <dlfcn.h>
#endif
#ifdef _WIN32
#define VSSCRIPT_SO "vsscript.dll"
#else
#define VSSCRIPT_SO "libvapoursynth-script.so"
#endif
// Allocate storage for and initialise static members
namespace {
bool vs_loaded = false;
#ifdef _WIN32
HINSTANCE hLib = nullptr;
#else
void* hLib = nullptr;
#endif
const VSAPI *api = nullptr;
VSSCRIPTAPI *scriptapi = nullptr;
std::mutex VapourSynthMutex;
}
typedef VSSCRIPTAPI* VS_CC FUNC(int);
VapourSynthWrapper::VapourSynthWrapper() {
// VSScript assumes it's only loaded once, so unlike AVS we can't unload it when the refcount reaches zero
if (!vs_loaded) {
vs_loaded = true;
#ifdef _WIN32
#define CONCATENATE(x, y) x ## y
#define _Lstr(x) CONCATENATE(L, x)
hLib = LoadLibraryW(_Lstr(VSSCRIPT_SO));
#undef _Lstr
#undef CONCATENATE
#else
hLib = dlopen(VSSCRIPT_SO, RTLD_LAZY | RTLD_GLOBAL | RTLD_DEEPBIND);
#endif
if (!hLib)
throw VapoursynthError("Could not load " VSSCRIPT_SO);
#ifdef _WIN32
FUNC* getVSScriptAPI = (FUNC*)GetProcAddress(hLib, "getVSScriptAPI");
#else
FUNC* getVSScriptAPI = (FUNC*)dlsym(hLib, "getVSScriptAPI");
#endif
if (!getVSScriptAPI)
throw VapoursynthError("Failed to get address of getVSScriptAPI from " VSSCRIPT_SO);
scriptapi = getVSScriptAPI(VSSCRIPT_API_VERSION);
if (!scriptapi)
throw VapoursynthError("Failed to get Vapoursynth ScriptAPI");
api = scriptapi->getVSAPI(VAPOURSYNTH_API_VERSION);
if (!api)
throw VapoursynthError("Failed to get Vapoursynth API");
}
}
std::mutex& VapourSynthWrapper::GetMutex() const {
return VapourSynthMutex;
}
const VSAPI *VapourSynthWrapper::GetAPI() const {
return api;
}
const VSSCRIPTAPI *VapourSynthWrapper::GetScriptAPI() const {
return scriptapi;
}
#endif

42
src/vapoursynth_wrap.h Normal file
View File

@ -0,0 +1,42 @@
// Copyright (c) 2022, arch1t3cht <arch1t3cht@gmail.com>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
/// @file vapoursynth_wrap.h
/// @see vapoursynth_wrap.cpp
/// @ingroup video_input audio_input
///
#ifdef WITH_VAPOURSYNTH
#include <libaegisub/exception.h>
DEFINE_EXCEPTION(VapoursynthError, agi::Exception);
struct VSAPI;
struct VSSCRIPTAPI;
namespace std { class mutex; }
class VapourSynthWrapper {
VapourSynthWrapper(VapourSynthWrapper const&);
public:
std::mutex& GetMutex() const;
const VSAPI *GetAPI() const;
const VSSCRIPTAPI *GetScriptAPI() const;
VapourSynthWrapper();
};
#endif

View File

@ -14,6 +14,8 @@
//
// Aegisub Project http://www.aegisub.org/
#pragma once
#include <vector>
class wxImage;

View File

@ -0,0 +1,204 @@
// Copyright (c) 2022, arch1t3cht <arch1t3cht@gmail.com>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
/// @file video_provider_bestsource.cpp
/// @brief BestSource-based video provider
/// @ingroup video_input bestsource
///
#ifdef WITH_BESTSOURCE
#include "include/aegisub/video_provider.h"
#include "videosource.h"
#include "audiosource.h"
#include "BSRational.h"
extern "C" {
#include <libavutil/frame.h>
#include <libavutil/pixfmt.h>
#include <libswscale/swscale.h>
}
#include "bestsource_common.h"
#include "options.h"
#include "compat.h"
#include "video_frame.h"
namespace agi { class BackgroundRunner; }
#include <libaegisub/fs.h>
#include <libaegisub/path.h>
#include <libaegisub/make_unique.h>
#include <libaegisub/background_runner.h>
#include <libaegisub/log.h>
namespace {
/// @class BSVideoProvider
/// @brief Implements video loading through BestSource.
class BSVideoProvider final : public VideoProvider {
std::map<std::string, std::string> bsopts;
BestVideoSource bs;
VideoProperties properties;
std::vector<int> Keyframes;
agi::vfr::Framerate Timecodes;
std::string colorspace;
bool has_audio = false;
public:
BSVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br);
void GetFrame(int n, VideoFrame &out) override;
void SetColorSpace(std::string const& matrix) override { } // TODO Follow Aegisub's colorspace forcing?
int GetFrameCount() const override { return properties.NumFrames; };
int GetWidth() const override { return properties.Width; };
int GetHeight() const override { return properties.Height; };
double GetDAR() const override { return ((double) properties.Width * properties.SAR.Num) / (properties.Height * properties.SAR.Den); };
agi::vfr::Framerate GetFPS() const override { return Timecodes; };
std::string GetColorSpace() const override { return colorspace; };
std::string GetRealColorSpace() const override { return colorspace; };
std::vector<int> GetKeyFrames() const override { return Keyframes; };
std::string GetDecoderName() const override { return "BestSource"; };
bool WantsCaching() const override { return false; };
bool HasAudio() const override { return has_audio; };
};
// Match the logic from the ffms2 provider, but directly use libavutil's constants and don't abort when encountering an unknown color space
std::string colormatrix_description(const AVFrame *frame) {
// Assuming TV for unspecified
std::string str = frame->color_range == AVCOL_RANGE_JPEG ? "PC" : "TV";
LOG_D("bestsource") << frame->colorspace;
switch (frame->colorspace) {
case AVCOL_SPC_BT709:
return str + ".709";
case AVCOL_SPC_FCC:
return str + ".FCC";
case AVCOL_SPC_BT470BG:
case AVCOL_SPC_SMPTE170M:
return str + ".601";
case AVCOL_SPC_SMPTE240M:
return str + ".240M";
default:
return "None";
}
}
BSVideoProvider::BSVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br) try
: bsopts()
, bs(filename.string(), "", -1, false, OPT_GET("Provider/Video/BestSource/Threads")->GetInt(), GetBSCacheFile(filename), &bsopts)
{
bs.SetMaxCacheSize(OPT_GET("Provider/Video/BestSource/Max Cache Size")->GetInt() << 20);
bs.SetSeekPreRoll(OPT_GET("Provider/Video/BestSource/Seek Preroll")->GetInt());
try {
BestAudioSource dummysource(filename.string(), -1, 0, "");
has_audio = true;
} catch (AudioException const& err) {
has_audio = false;
}
properties = bs.GetVideoProperties();
if (properties.NumFrames == -1) {
LOG_D("bs") << "File not cached or varying samples, creating cache.";
br->Run([&](agi::ProgressSink *ps) {
ps->SetTitle(from_wx(_("Exacting")));
ps->SetMessage(from_wx(_("Creating cache... This can take a while!")));
ps->SetIndeterminate();
if (bs.GetExactDuration()) {
LOG_D("bs") << "File cached and has exact samples.";
}
});
properties = bs.GetVideoProperties();
}
br->Run([&](agi::ProgressSink *ps) {
ps->SetTitle(from_wx(_("Scanning")));
ps->SetMessage(from_wx(_("Finding Keyframes and Timecodes...")));
std::vector<int> TimecodesVector;
for (int n = 0; n < properties.NumFrames; n++) {
if (ps->IsCancelled()) {
return;
}
std::unique_ptr<BestVideoFrame> frame(bs.GetFrame(n));
if (frame == nullptr) {
throw VideoOpenError("Couldn't read frame!");
}
if (frame->GetAVFrame()->key_frame) {
Keyframes.push_back(n);
}
TimecodesVector.push_back((int) frame->GetAVFrame()->pts);
ps->SetProgress(n, properties.NumFrames);
}
if (TimecodesVector.size() < 2 || TimecodesVector.front() == TimecodesVector.back()) {
Timecodes = (double) properties.FPS.Num / properties.FPS.Den;
} else {
Timecodes = agi::vfr::Framerate(TimecodesVector);
}
});
// Decode the first frame to get the color space
std::unique_ptr<BestVideoFrame> frame(bs.GetFrame(0));
colorspace = colormatrix_description(frame->GetAVFrame());
}
catch (VideoException const& err) {
throw VideoOpenError("Failed to create BestVideoSource");
}
void BSVideoProvider::GetFrame(int n, VideoFrame &out) {
std::unique_ptr<BestVideoFrame> bsframe(bs.GetFrame(n));
if (bsframe == nullptr) {
throw VideoDecodeError("Couldn't read frame!");
}
const AVFrame *frame = bsframe->GetAVFrame();
SwsContext *context = sws_getContext(
frame->width, frame->height, (AVPixelFormat) frame->format, // TODO figure out aegi's color space forcing.
frame->width, frame->height, AV_PIX_FMT_BGR0,
SWS_BICUBIC, nullptr, nullptr, nullptr);
if (context == nullptr) {
throw VideoDecodeError("Couldn't convert frame!");
}
out.data.resize(frame->width * frame->height * 4);
uint8_t *data[1] = {&out.data[0]};
int stride[1] = {frame->width * 4};
sws_scale(context, frame->data, frame->linesize, 0, frame->height, data, stride);
out.width = frame->width;
out.height = frame->height;
out.pitch = stride[0];
out.flipped = false; // TODO figure out flipped
sws_freeContext(context);
}
}
std::unique_ptr<VideoProvider> CreateBSVideoProvider(agi::fs::path const& path, std::string const& colormatrix, agi::BackgroundRunner *br) {
return agi::make_unique<BSVideoProvider>(path, colormatrix, br);
}
#endif /* WITH_BESTSOURCE */

View File

@ -29,6 +29,8 @@ std::unique_ptr<VideoProvider> CreateDummyVideoProvider(agi::fs::path const&, st
std::unique_ptr<VideoProvider> CreateYUV4MPEGVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *);
std::unique_ptr<VideoProvider> CreateFFmpegSourceVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *);
std::unique_ptr<VideoProvider> CreateAvisynthVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *);
std::unique_ptr<VideoProvider> CreateBSVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *);
std::unique_ptr<VideoProvider> CreateVapoursynthVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *);
std::unique_ptr<VideoProvider> CreateCacheVideoProvider(std::unique_ptr<VideoProvider>);
@ -47,6 +49,12 @@ namespace {
#endif
#ifdef WITH_AVISYNTH
{"Avisynth", CreateAvisynthVideoProvider, false},
#endif
#ifdef WITH_BESTSOURCE
{"BestSource", CreateBSVideoProvider, false},
#endif
#ifdef WITH_VAPOURSYNTH
{"Vapoursynth", CreateVapoursynthVideoProvider, false},
#endif
};
}

282
src/video_provider_vs.cpp Normal file
View File

@ -0,0 +1,282 @@
// Copyright (c) 2022, arch1t3cht <arch1t3cht@gmail.com>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
#ifdef WITH_VAPOURSYNTH
#include "include/aegisub/video_provider.h"
#include "options.h"
#include "video_frame.h"
#include <libaegisub/access.h>
#include <libaegisub/format.h>
#include <libaegisub/fs.h>
#include <libaegisub/path.h>
#include <libaegisub/make_unique.h>
#include <mutex>
#include "vapoursynth_wrap.h"
#include "VSScript4.h"
#include "VSHelper4.h"
#include "VSConstants4.h"
namespace {
class VapoursynthVideoProvider: public VideoProvider {
VapourSynthWrapper vs;
VSScript *script = nullptr;
VSNode *node = nullptr;
const VSVideoInfo *vi = nullptr;
double dar = 0;
agi::vfr::Framerate fps;
std::vector<int> keyframes;
std::string colorspace;
std::string real_colorspace;
const VSFrame *GetVSFrame(int n);
void SetResizeArg(VSMap *args, const VSMap *props, const char *arg_name, const char *prop_name, int deflt, int unspecified = -1);
public:
VapoursynthVideoProvider(agi::fs::path const& filename, std::string const& colormatrix);
~VapoursynthVideoProvider();
void GetFrame(int n, VideoFrame &frame) override;
void SetColorSpace(std::string const& matrix) override { }
int GetFrameCount() const override { return vi->numFrames; }
agi::vfr::Framerate GetFPS() const override { return fps; }
int GetWidth() const override { return vi->width; }
int GetHeight() const override { return vi->height; }
double GetDAR() const override { return dar; }
std::vector<int> GetKeyFrames() const override { return keyframes; }
std::string GetColorSpace() const override { return colorspace; }
std::string GetRealColorSpace() const override { return colorspace; }
bool HasAudio() const override { return false; }
virtual bool WantsCaching() const override { return true; }
virtual std::string GetDecoderName() const override { return "VapourSynth"; }
};
std::string colormatrix_description(int colorFamily, int colorRange, int matrix) {
if (colorFamily != cfYUV) {
return "None";
}
// Assuming TV for unspecified
std::string str = colorRange == VSC_RANGE_FULL ? "PC" : "TV";
switch (matrix) {
case VSC_MATRIX_RGB:
return "None";
case VSC_MATRIX_BT709:
return str + ".709";
case VSC_MATRIX_FCC:
return str + ".FCC";
case VSC_MATRIX_BT470_BG:
case VSC_MATRIX_ST170_M:
return str + ".601";
case VSC_MATRIX_ST240_M:
return str + ".240M";
default:
return "None";
}
}
// Adds an argument to the rescaler if the corresponding frameprop does not exist or is set as unspecified
void VapoursynthVideoProvider::SetResizeArg(VSMap *args, const VSMap *props, const char *arg_name, const char *prop_name, int deflt, int unspecified) {
int err;
int result = vs.GetAPI()->mapGetInt(props, prop_name, 0, &err);
if (err != 0 || result == unspecified) {
result = deflt;
vs.GetAPI()->mapSetInt(args, arg_name, result, maAppend);
}
}
VapoursynthVideoProvider::VapoursynthVideoProvider(agi::fs::path const& filename, std::string const& colormatrix) try {
agi::acs::CheckFileRead(filename);
std::lock_guard<std::mutex> lock(vs.GetMutex());
script = vs.GetScriptAPI()->createScript(nullptr);
if (script == nullptr) {
throw VapoursynthError("Error creating script API");
}
vs.GetScriptAPI()->evalSetWorkingDir(script, 1);
if (vs.GetScriptAPI()->evaluateFile(script, filename.string().c_str())) {
std::string msg = agi::format("Error executing VapourSynth script: %s", vs.GetScriptAPI()->getError(script));
vs.GetScriptAPI()->freeScript(script);
throw VapoursynthError(msg);
}
node = vs.GetScriptAPI()->getOutputNode(script, 0);
if (node == nullptr) {
vs.GetScriptAPI()->freeScript(script);
throw VapoursynthError("No output node set");
}
if (vs.GetAPI()->getNodeType(node) != mtVideo) {
vs.GetAPI()->freeNode(node);
vs.GetScriptAPI()->freeScript(script);
throw VapoursynthError("Output node isn't a video node");
}
vi = vs.GetAPI()->getVideoInfo(node);
if (!vsh::isConstantVideoFormat(vi)) {
vs.GetAPI()->freeNode(node);
vs.GetScriptAPI()->freeScript(script);
throw VapoursynthError("Video doesn't have constant format");
}
// Assume constant frame rate, since handling VFR would require going through all frames when loading.
// Users can load custom timecodes files to deal with VFR.
// Alternatively (TODO) the provider could read timecodes and keyframes from a second output node.
fps = (double) vi->fpsNum / vi->fpsDen;
// Find the first frame to get some info
const VSFrame *frame;
try {
frame = GetVSFrame(0);
} catch (VapoursynthError const& err) {
vs.GetAPI()->freeNode(node);
vs.GetScriptAPI()->freeScript(script);
throw err;
}
int err1, err2;
const VSMap *props = vs.GetAPI()->getFramePropertiesRO(frame);
int sarn = vs.GetAPI()->mapGetInt(props, "_SARNum", 0, &err1);
int sard = vs.GetAPI()->mapGetInt(props, "_SARDen", 0, &err2);
if (!err1 && !err2) {
dar = ((double) vi->width * sarn) / (vi->height * sard);
}
int range = vs.GetAPI()->mapGetInt(props, "_ColorRange", 0, &err1);
int matrix = vs.GetAPI()->mapGetInt(props, "_Matrix", 0, &err2);
colorspace = colormatrix_description(vi->format.colorFamily, err1 == 0 ? range : -1, err2 == 0 ? matrix : -1);
vs.GetAPI()->freeFrame(frame);
if (vi->format.colorFamily != cfRGB || vi->format.bitsPerSample != 8) {
// Convert to RGB24 format
VSPlugin *resize = vs.GetAPI()->getPluginByID(VSH_RESIZE_PLUGIN_ID, vs.GetScriptAPI()->getCore(script));
if (resize == nullptr) {
throw VapoursynthError("Couldn't find resize plugin");
}
VSMap *args = vs.GetAPI()->createMap();
if (args == nullptr) {
throw VapoursynthError("Failed to create argument map");
}
vs.GetAPI()->mapSetNode(args, "clip", node, maAppend);
vs.GetAPI()->mapSetInt(args, "format", pfRGB24, maAppend);
if (vi->format.colorFamily != cfGray)
SetResizeArg(args, props, "matrix_in", "_Matrix", VSC_MATRIX_BT709, VSC_MATRIX_UNSPECIFIED);
SetResizeArg(args, props, "transfer_in", "_Transfer", VSC_TRANSFER_BT709, VSC_TRANSFER_UNSPECIFIED);
SetResizeArg(args, props, "primaries_in", "_Primaries", VSC_PRIMARIES_BT709, VSC_PRIMARIES_UNSPECIFIED);
SetResizeArg(args, props, "range_in", "_ColorRange", VSC_RANGE_LIMITED);
SetResizeArg(args, props, "chromaloc_in", "_ChromaLocation", VSC_CHROMA_LEFT);
VSMap *result = vs.GetAPI()->invoke(resize, "Bicubic", args);
vs.GetAPI()->freeMap(args);
const char *error = vs.GetAPI()->mapGetError(result);
if (error) {
vs.GetAPI()->freeMap(result);
vs.GetAPI()->freeNode(node);
vs.GetScriptAPI()->freeScript(script);
throw VideoProviderError(agi::format("Failed to convert to RGB24: %s", error));
}
int err;
vs.GetAPI()->freeNode(node);
node = vs.GetAPI()->mapGetNode(result, "clip", 0, &err);
vs.GetAPI()->freeMap(result);
if (err) {
vs.GetScriptAPI()->freeScript(script);
throw VideoProviderError("Failed to get resize output node");
}
// Finally, try to get the first frame again, so if the filter does crash, it happens before loading finishes
const VSFrame *rgbframe;
try {
rgbframe = GetVSFrame(0);
} catch (VapoursynthError const& err) {
vs.GetAPI()->freeNode(node);
vs.GetScriptAPI()->freeScript(script);
throw err;
}
vs.GetAPI()->freeFrame(rgbframe);
}
}
catch (VapoursynthError const& err) {
throw VideoProviderError(agi::format("Vapoursynth error: %s", err.GetMessage()));
}
const VSFrame *VapoursynthVideoProvider::GetVSFrame(int n) {
char errorMsg[1024];
const VSFrame *frame = vs.GetAPI()->getFrame(n, node, errorMsg, sizeof(errorMsg));
if (frame == nullptr) {
throw VapoursynthError(agi::format("Error getting frame: %s", errorMsg));
}
return frame;
}
void VapoursynthVideoProvider::GetFrame(int n, VideoFrame &out) {
std::lock_guard<std::mutex> lock(vs.GetMutex());
const VSFrame *frame = GetVSFrame(n);
const VSVideoFormat *format = vs.GetAPI()->getVideoFrameFormat(frame);
if (format->colorFamily != cfRGB || format->numPlanes != 3 || format->bitsPerSample != 8 || format->subSamplingH != 0 || format->subSamplingW != 0) {
throw VapoursynthError("Frame not in RGB24 format");
}
out.width = vs.GetAPI()->getFrameWidth(frame, 0);
out.height = vs.GetAPI()->getFrameHeight(frame, 0);
out.pitch = out.width * 4;
out.flipped = false;
out.data.resize(out.pitch * out.height);
for (int p = 0; p < format->numPlanes; p++) {
ptrdiff_t stride = vs.GetAPI()->getStride(frame, p);
const uint8_t *readPtr = vs.GetAPI()->getReadPtr(frame, p);
uint8_t *writePtr = &out.data[2 - p];
int rows = vs.GetAPI()->getFrameHeight(frame, p);
int cols = vs.GetAPI()->getFrameWidth(frame, p);
for (int row = 0; row < rows; row++) {
const uint8_t *rowPtr = readPtr;
uint8_t *rowWritePtr = writePtr;
for (int col = 0; col < cols; col++) {
*rowWritePtr = *rowPtr++;
rowWritePtr += 4;
}
readPtr += stride;
writePtr += out.pitch;
}
}
vs.GetAPI()->freeFrame(frame);
}
VapoursynthVideoProvider::~VapoursynthVideoProvider() {
if (node != nullptr) {
vs.GetAPI()->freeNode(node);
}
if (script != nullptr) {
vs.GetScriptAPI()->freeScript(script);
}
}
}
namespace agi { class BackgroundRunner; }
std::unique_ptr<VideoProvider> CreateVapoursynthVideoProvider(agi::fs::path const& path, std::string const& colormatrix, agi::BackgroundRunner *) {
return agi::make_unique<VapoursynthVideoProvider>(path, colormatrix);
}
#endif // HAVE_VAPOURSYNTH

View File

@ -0,0 +1,7 @@
[wrap-git]
url = https://github.com/vapoursynth/bestsource
revision = head
patch_directory = bestsource
[provide]
bestsource = bestsource_dep

4
subprojects/jansson.wrap Normal file
View File

@ -0,0 +1,4 @@
[wrap-git]
directory = jansson
url = https://github.com/akheron/jansson.git
revision = v2.14

View File

@ -0,0 +1,156 @@
// Since we don't use ExportAsPlanar, we don't actually need libp2p.
// So instead of adding another wrap and meson build file, and *also*
// patching the include statement in videosource.cpp, we throw a dummy
// header file in the folder that should contain the checkout of libp2p.
#ifndef P2P_API_H_
#define P2P_API_H_
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Notation: [Xa-Ya-Za]
*
* [] denotes a machine word of the specified endianness. Xa-Ya-Za denote
* component X, Y, and Z packed in the word, with bit depths a, b, c, in order
* from MSB to LSB. Padding bits are represented by the component '!'.
*/
enum p2p_packing {
/** [R8-G8-B8] */
p2p_rgb24_be, /* RGB */
p2p_rgb24_le, /* BGR */
p2p_rgb24,
/** [A8-R8-G8-B8] */
p2p_argb32_be, /* ARGB */
p2p_argb32_le, /* BGRA */
p2p_argb32,
/** [A8-Y8-U8-V8] */
p2p_ayuv_be, /* AYUV */
p2p_ayuv_le, /* VUYA */
p2p_ayuv,
/** [R16-G16-B16] */
p2p_rgb48_be, /* RGB, big-endian components */
p2p_rgb48_le, /* BGR, little-endian components */
p2p_rgb48,
/** [A16-R16-G16-B16] */
p2p_argb64_be, /* ARGB big-endian components */
p2p_argb64_le, /* BGRA little-endian components */
p2p_argb64,
/** [A2-R10-G10-B10] */
p2p_rgb30_be, /* ARGB packed in big-endian DWORD */
p2p_rgb30_le, /* ARGB packed in little-endian DWORD */
p2p_rgb30,
/** [A2-V10-Y10-U10] */
p2p_y410_be, /* AVYU packed in big-endian DWORD */
p2p_y410_le, /* AVYU packed in little-endian DWORD */
p2p_y410,
/** [A16-V16-Y16-U16] */
p2p_y416_be, /* AVYU, big-endian components */
p2p_y416_le, /* UYVA, little-endian components */
p2p_y416,
/** [Y8] [U8] [Y8] [V8] */
p2p_yuy2,
/** [U8] [Y8] [V8] [Y8] */
p2p_uyvy,
/** [Y10-!6] [U10-!6] [Y10-!6] [V10-!6] */
p2p_y210_be, /* YUYV, big-endian components, lower 6 bits zero */
p2p_y210_le, /* YUYV, little-endian components, lower 6 bits zero. Microsoft Y210. */
p2p_y210,
/** [Y16] [U16] [Y16] [V16] */
p2p_y216_be, /* YUYV, big-endian components */
p2p_y216_le, /* YUYV, little-endian components. Microsoft Y216. */
p2p_y216,
/** [!2-V10-Y10-U10] [!2-Y10-U10-Y10] [!2-U10-Y10-V10] [!2-Y10-V10-Y10] */
p2p_v210_be, /* v210 with big-endian DWORDs */
p2p_v210_le, /* Apple/QuickTime v210 */
p2p_v210,
/** [U16] [Y16] [V16] [Y16] */
p2p_v216_be, /* UYVY, big-endian components */
p2p_v216_le, /* UYVY, little-endian components. Apple/QuickTime v216. */
p2p_v216,
/** [U8-V8] */
p2p_nv12_be, /* aka NV21, V first */
p2p_nv12_le, /* NV12 */
p2p_nv12,
/** [U10-!6-V10-!6] */
p2p_p010_be, /* NV21, big-endian components, lower 6 bits zero */
p2p_p010_le, /* NV12, little-endian components, lower 6 bits zero. Microsoft P010. */
p2p_p010,
/** [U16-V16] */
p2p_p016_be, /* NV21, big-endian components */
p2p_p016_le, /* NV12, little-endian components. Microsoft P016. */
p2p_p016,
/** [U10-!6-V10-!6] */
p2p_p210_be, /* NV21, big-endian components, lower 6 bits zero */
p2p_p210_le, /* NV12, little-endian components, lower 6 bits zero. Microsoft P210. */
p2p_p210,
/** [U16-V16] */
p2p_p216_be, /* NV21, big-endian components */
p2p_p216_le, /* NV12, little-endian components. Microsoft P216. */
p2p_p216,
/** [R8-G8-B8-A8] */
p2p_rgba32_be, /* RGBA */
p2p_rgba32_le, /* ABGR */
p2p_rgba32,
/** [R16-G16-B16-A16] */
p2p_rgba64_be, /* RGBA, big-endian components */
p2p_rgba64_le, /* ABGR, little-endian components */
p2p_rgba64,
/** [A16-B16-G16-R16] */
p2p_abgr64_be, /* ABGR, big-endian components */
p2p_abgr64_le, /* RGBA, little-endian components */
p2p_abgr64,
/** [B16-G16-R16] */
p2p_bgr48_be, /* BGR, big-endian components */
p2p_bgr48_le, /* RGB, little-endian components */
p2p_bgr48,
/** [B16-G16-R16-A16] */
p2p_bgra64_be, /* BGRA, big-endian components */
p2p_bgra64_le, /* ARGB, little-endian components */
p2p_bgra64,
p2p_packing_max,
};
struct p2p_buffer_param {
/**
* Planar order: R-G-B-A or Y-U-V-A. Alpha is optional.
* Packed order: Y-UV if NV12/21, else single plane. Y optional for NV12/21.
*/
const void *src[4];
void *dst[4];
ptrdiff_t src_stride[4];
ptrdiff_t dst_stride[4];
unsigned width;
unsigned height;
enum p2p_packing packing;
};
/** Pack/unpack a range of pixels from a scanline. */
typedef void (*p2p_unpack_func)(const void *src, void * const dst[4], unsigned left, unsigned right);
typedef void (*p2p_pack_func)(const void * const src[4], void *dst, unsigned left, unsigned right);
/** Select a line pack/unpack function. */
p2p_unpack_func p2p_select_unpack_func(enum p2p_packing packing);
p2p_pack_func p2p_select_pack_func(enum p2p_packing packing);
p2p_pack_func p2p_select_pack_func_ex(enum p2p_packing packing, int alpha_one_fill);
/** When processing formats like NV12, ignore the unpacked plane. */
#define P2P_SKIP_UNPACKED_PLANES (1UL << 0)
/** When packing, store a bit pattern of all ones in the alpha channel instead of all zeros. */
#define P2P_ALPHA_SET_ONE (1UL << 1)
/** Helper function to pack/unpack between memory locations. */
void p2p_unpack_frame(const struct p2p_buffer_param *param, unsigned long flags) {};
void p2p_pack_frame(const struct p2p_buffer_param *param, unsigned long flags) {};
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* P2P_API_H_ */

View File

@ -0,0 +1,36 @@
project('BestSource', 'cpp',
default_options: ['buildtype=release', 'b_ndebug=if-release', 'cpp_std=c++14'],
meson_version: '>=0.48.0'
)
cmake = import('cmake')
sources = [
'src/audiosource.cpp',
'src/videosource.cpp',
'src/SrcAttribCache.cpp',
'src/BSRational.cpp',
]
deps = [
dependency('libavcodec'),
dependency('libavformat'),
dependency('libavutil'),
]
jansson_dep = dependency('jansson', version: '>= 2.7', required: false)
if jansson_dep.found()
deps += jansson_dep
else
jansson = cmake.subproject('jansson')
deps += jansson.dependency('jansson')
endif
bs_lib = static_library('bestsource', sources,
dependencies: deps,
gnu_symbol_visibility: 'hidden'
)
bestsource_dep = declare_dependency(link_with: bs_lib, include_directories: include_directories('src'))

View File

@ -0,0 +1,3 @@
project('vapoursynth', 'cpp')
vs_inc = include_directories('include')

View File

@ -0,0 +1,4 @@
[wrap-git]
url = https://github.com/Vapoursynth/vapoursynth.git
revision = R59
patch_directory = vapoursynth

View File

@ -172,21 +172,21 @@ TEST(lagi_audio, save_audio_clip_out_of_audio_range) {
TEST(lagi_audio, get_with_volume) {
TestAudioProvider<> provider;
uint16_t buff[4];
int16_t buff[4];
provider.GetAudioWithVolume(buff, 0, 4, 1.0);
provider.GetInt16MonoAudioWithVolume(buff, 0, 4, 1.0);
EXPECT_EQ(0, buff[0]);
EXPECT_EQ(1, buff[1]);
EXPECT_EQ(2, buff[2]);
EXPECT_EQ(3, buff[3]);
provider.GetAudioWithVolume(buff, 0, 4, 0.0);
provider.GetInt16MonoAudioWithVolume(buff, 0, 4, 0.0);
EXPECT_EQ(0, buff[0]);
EXPECT_EQ(0, buff[1]);
EXPECT_EQ(0, buff[2]);
EXPECT_EQ(0, buff[3]);
provider.GetAudioWithVolume(buff, 0, 4, 2.0);
provider.GetInt16MonoAudioWithVolume(buff, 0, 4, 2.0);
EXPECT_EQ(0, buff[0]);
EXPECT_EQ(2, buff[1]);
EXPECT_EQ(4, buff[2]);
@ -195,8 +195,8 @@ TEST(lagi_audio, get_with_volume) {
TEST(lagi_audio, volume_should_clamp_rather_than_wrap) {
TestAudioProvider<> provider;
uint16_t buff[1];
provider.GetAudioWithVolume(buff, 30000, 1, 2.0);
int16_t buff[1];
provider.GetInt16MonoAudioWithVolume(buff, 30000, 1, 2.0);
EXPECT_EQ(SHRT_MAX, buff[0]);
}
@ -232,7 +232,7 @@ TEST(lagi_audio, convert_8bit) {
auto provider = agi::CreateConvertAudioProvider(agi::make_unique<TestAudioProvider<uint8_t>>());
int16_t data[256];
provider->GetAudio(data, 0, 256);
provider->GetInt16MonoAudio(data, 0, 256);
for (int i = 0; i < 256; ++i)
ASSERT_EQ((i - 128) * 256, data[i]);
}
@ -243,13 +243,13 @@ TEST(lagi_audio, convert_32bit) {
auto provider = agi::CreateConvertAudioProvider(std::move(src));
int16_t sample;
provider->GetAudio(&sample, 0, 1);
provider->GetInt16MonoAudio(&sample, 0, 1);
EXPECT_EQ(SHRT_MIN, sample);
provider->GetAudio(&sample, 1LL << 31, 1);
provider->GetInt16MonoAudio(&sample, 1LL << 31, 1);
EXPECT_EQ(0, sample);
provider->GetAudio(&sample, (1LL << 32) - 1, 1);
provider->GetInt16MonoAudio(&sample, (1LL << 32) - 1, 1);
EXPECT_EQ(SHRT_MAX, sample);
}
@ -310,10 +310,10 @@ TEST(lagi_audio, stereo_downmix) {
};
auto provider = agi::CreateConvertAudioProvider(agi::make_unique<AudioProvider>());
EXPECT_EQ(1, provider->GetChannels());
EXPECT_EQ(2, provider->GetChannels());
int16_t samples[100];
provider->GetAudio(samples, 0, 100);
provider->GetInt16MonoAudio(samples, 0, 100);
for (int i = 0; i < 100; ++i)
EXPECT_EQ(i, samples[i]);
}
@ -333,27 +333,27 @@ struct FloatAudioProvider : agi::AudioProvider {
auto out = static_cast<Float *>(buf);
for (int64_t end = start + count; start < end; ++start) {
auto shifted = start + SHRT_MIN;
*out++ = (Float)(1.0 * shifted / (shifted < 0 ? -SHRT_MIN : SHRT_MAX));
*out++ = (Float)(shifted) / (-SHRT_MIN);
}
}
};
TEST(lagi_audio, float_conversion) {
auto provider = agi::CreateConvertAudioProvider(agi::make_unique<FloatAudioProvider<float>>());
EXPECT_FALSE(provider->AreSamplesFloat());
EXPECT_TRUE(provider->AreSamplesFloat());
int16_t samples[1 << 16];
provider->GetAudio(samples, 0, 1 << 16);
provider->GetInt16MonoAudio(samples, 0, 1 << 16);
for (int i = 0; i < (1 << 16); ++i)
ASSERT_EQ(i + SHRT_MIN, samples[i]);
}
TEST(lagi_audio, double_conversion) {
auto provider = agi::CreateConvertAudioProvider(agi::make_unique<FloatAudioProvider<double>>());
EXPECT_FALSE(provider->AreSamplesFloat());
EXPECT_TRUE(provider->AreSamplesFloat());
int16_t samples[1 << 16];
provider->GetAudio(samples, 0, 1 << 16);
provider->GetInt16MonoAudio(samples, 0, 1 << 16);
for (int i = 0; i < (1 << 16); ++i)
ASSERT_EQ(i + SHRT_MIN, samples[i]);
}
@ -551,4 +551,4 @@ TEST(lagi_audio, wave64_truncated) {
}
agi::fs::Remove(path);
}
}