CI-Build 2022-08-16
This commit is contained in:
commit
860a090de8
51 changed files with 2271 additions and 764 deletions
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
@ -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
3
.gitignore
vendored
|
@ -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
|
||||
|
|
541
CMakeLists.txt
541
CMakeLists.txt
|
@ -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
|
12
README.md
12
README.md
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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([&] {
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
|
|
75
meson.build
75
meson.build
|
@ -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
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
694
src/audio_player_xaudio2.cpp
Normal file
694
src/audio_player_xaudio2.cpp
Normal 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
|
89
src/audio_provider_bestsource.cpp
Normal file
89
src/audio_provider_bestsource.cpp
Normal 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 */
|
||||
|
|
@ -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
167
src/audio_provider_vs.cpp
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
49
src/bestsource_common.cpp
Normal 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
28
src/bestsource_common.h
Normal 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 */
|
|
@ -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);
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
105
src/vapoursynth_wrap.cpp
Normal 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
42
src/vapoursynth_wrap.h
Normal 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
|
|
@ -14,6 +14,8 @@
|
|||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
class wxImage;
|
||||
|
|
204
src/video_provider_bestsource.cpp
Normal file
204
src/video_provider_bestsource.cpp
Normal 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 */
|
|
@ -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
282
src/video_provider_vs.cpp
Normal 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
|
7
subprojects/bestsource.wrap
Normal file
7
subprojects/bestsource.wrap
Normal 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
4
subprojects/jansson.wrap
Normal file
|
@ -0,0 +1,4 @@
|
|||
[wrap-git]
|
||||
directory = jansson
|
||||
url = https://github.com/akheron/jansson.git
|
||||
revision = v2.14
|
156
subprojects/packagefiles/bestsource/libp2p/p2p_api.h
Normal file
156
subprojects/packagefiles/bestsource/libp2p/p2p_api.h
Normal 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_ */
|
36
subprojects/packagefiles/bestsource/meson.build
Normal file
36
subprojects/packagefiles/bestsource/meson.build
Normal 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'))
|
||||
|
3
subprojects/packagefiles/vapoursynth/meson.build
Normal file
3
subprojects/packagefiles/vapoursynth/meson.build
Normal file
|
@ -0,0 +1,3 @@
|
|||
project('vapoursynth', 'cpp')
|
||||
|
||||
vs_inc = include_directories('include')
|
4
subprojects/vapoursynth.wrap
Normal file
4
subprojects/vapoursynth.wrap
Normal file
|
@ -0,0 +1,4 @@
|
|||
[wrap-git]
|
||||
url = https://github.com/Vapoursynth/vapoursynth.git
|
||||
revision = R59
|
||||
patch_directory = vapoursynth
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue