From 33b9a6e395198ac856c932404a65e6e049d6b146 Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Mon, 8 Aug 2022 02:14:31 +0200 Subject: [PATCH] Add bestsource video provider Co-authored-by: Asada shinon --- .gitignore | 1 + meson.build | 17 ++ meson_options.txt | 1 + src/meson.build | 4 + src/video_frame.h | 2 + src/video_provider_bestsource.cpp | 188 ++++++++++++++++++ src/video_provider_manager.cpp | 4 + subprojects/bestsource.wrap | 7 + .../packagefiles/bestsource/libp2p/p2p_api.h | 156 +++++++++++++++ .../packagefiles/bestsource/meson.build | 28 +++ 10 files changed, 408 insertions(+) create mode 100644 src/video_provider_bestsource.cpp create mode 100644 subprojects/bestsource.wrap create mode 100644 subprojects/packagefiles/bestsource/libp2p/p2p_api.h create mode 100644 subprojects/packagefiles/bestsource/meson.build diff --git a/.gitignore b/.gitignore index a2d7c9b86..fd239df2d 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ tools/repack-thes-dict.dSYM # Meson build*/ +subprojects/bestsource/ subprojects/boost*/ subprojects/cairo* subprojects/ffmpeg diff --git a/meson.build b/meson.build index 9029f5070..6115d1329 100644 --- a/meson.build +++ b/meson.build @@ -220,6 +220,23 @@ 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') + 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 host_machine.system() == 'windows' and get_option('avisynth').enabled() conf.set('WITH_AVISYNTH', 1) # bundled separately with installer endif diff --git a/meson_options.txt b/meson_options.txt index 3bc0461cd..19c447af1 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -7,6 +7,7 @@ option('default_audio_output', type: 'combo', choices: ['auto', 'ALSA', 'OpenAL' 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('fftw3', type: 'feature', description: 'FFTW3 support') option('hunspell', type: 'feature', description: 'Hunspell spell checker') diff --git a/src/meson.build b/src/meson.build index 72587d366..2d5145dbf 100644 --- a/src/meson.build +++ b/src/meson.build @@ -224,6 +224,10 @@ if conf.has('WITH_CSRI') aegisub_src += files('subtitles_provider_csri.cpp') endif +if conf.has('WITH_BESTSOURCE') + aegisub_src += files('video_provider_bestsource.cpp') +endif + opt_src = [ ['ALSA', 'audio_player_alsa.cpp'], ['PortAudio', 'audio_player_portaudio.cpp'], diff --git a/src/video_frame.h b/src/video_frame.h index 2a47ed69c..2f4f21e90 100644 --- a/src/video_frame.h +++ b/src/video_frame.h @@ -14,6 +14,8 @@ // // Aegisub Project http://www.aegisub.org/ +#pragma once + #include class wxImage; diff --git a/src/video_provider_bestsource.cpp b/src/video_provider_bestsource.cpp new file mode 100644 index 000000000..8bf8786d5 --- /dev/null +++ b/src/video_provider_bestsource.cpp @@ -0,0 +1,188 @@ +// Copyright (c) 2022, arch1t3cht +// +// 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 "BSRational.h" + +extern "C" { +#include +#include +#include +} + +/* #include "utils.h" */ +#include "options.h" +#include "compat.h" +#include "video_frame.h" +namespace agi { class BackgroundRunner; } + +#include +#include +#include +#include +#include + +#include +#include + +namespace { + +/// @class BSVideoProvider +/// @brief Implements video loading through BestSource. +class BSVideoProvider final : public VideoProvider { + std::map bsopts; + BestVideoSource bs; + VideoProperties properties; + + std::vector Keyframes; + agi::vfr::Framerate Timecodes; + + std::string GetCacheFile(agi::fs::path const& filename); + +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 { } + + 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 "TV.709"; }; // TODO + std::string GetRealColorSpace() const override { return "TV.709"; }; + std::vector GetKeyFrames() const override { return Keyframes; }; + std::string GetDecoderName() const override { return "BestSource"; }; + bool WantsCaching() const override { return false; }; + bool HasAudio() const override { return false; }; +}; + +BSVideoProvider::BSVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br) try +: bsopts() +, bs(filename.string(), "", -1, false, 0, GetCacheFile(filename), &bsopts) +{ + + 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 TimecodesVector; + for (int n = 0; n < properties.NumFrames; n++) { + if (ps->IsCancelled()) { + return; + } + std::unique_ptr 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); + } + }); +} +catch (VideoException const& err) { + throw VideoOpenError("Failed to create BestVideoSource"); +} + +std::string BSVideoProvider::GetCacheFile(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(); +} + +void BSVideoProvider::GetFrame(int n, VideoFrame &out) { + std::unique_ptr bsframe(bs.GetFrame(n)); + if (bsframe == nullptr) { + throw VideoDecodeError("Couldn't read frame!"); + } + const AVFrame *frame = bsframe->GetAVFrame(); + AVFrame *newframe = av_frame_alloc(); + + 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!"); + } + + sws_scale_frame(context, newframe, frame); + + out.width = newframe->width; + out.height = newframe->height; + out.pitch = newframe->width * 4; + out.flipped = false; // TODO figure out flipped + + out.data.assign(newframe->data[0], newframe->data[0] + newframe->linesize[0] * newframe->height); + + sws_freeContext(context); + av_frame_free(&newframe); +} + +} + +std::unique_ptr CreateBSVideoProvider(agi::fs::path const& path, std::string const& colormatrix, agi::BackgroundRunner *br) { + return agi::make_unique(path, colormatrix, br); +} + +#endif /* WITH_BESTSOURCE */ diff --git a/src/video_provider_manager.cpp b/src/video_provider_manager.cpp index 3739a7293..8ec7c18fc 100644 --- a/src/video_provider_manager.cpp +++ b/src/video_provider_manager.cpp @@ -29,6 +29,7 @@ std::unique_ptr CreateDummyVideoProvider(agi::fs::path const&, st std::unique_ptr CreateYUV4MPEGVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *); std::unique_ptr CreateFFmpegSourceVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *); std::unique_ptr CreateAvisynthVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *); +std::unique_ptr CreateBSVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *); std::unique_ptr CreateCacheVideoProvider(std::unique_ptr); @@ -47,6 +48,9 @@ namespace { #endif #ifdef WITH_AVISYNTH {"Avisynth", CreateAvisynthVideoProvider, false}, +#endif +#ifdef WITH_BESTSOURCE + {"BestSource", CreateBSVideoProvider, false}, #endif }; } diff --git a/subprojects/bestsource.wrap b/subprojects/bestsource.wrap new file mode 100644 index 000000000..fa0e5ef45 --- /dev/null +++ b/subprojects/bestsource.wrap @@ -0,0 +1,7 @@ +[wrap-git] +url = https://github.com/vapoursynth/bestsource +revision = head +patch_directory = bestsource + +[provide] +bestsource = bestsource_dep diff --git a/subprojects/packagefiles/bestsource/libp2p/p2p_api.h b/subprojects/packagefiles/bestsource/libp2p/p2p_api.h new file mode 100644 index 000000000..f66166538 --- /dev/null +++ b/subprojects/packagefiles/bestsource/libp2p/p2p_api.h @@ -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 + +#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_ */ diff --git a/subprojects/packagefiles/bestsource/meson.build b/subprojects/packagefiles/bestsource/meson.build new file mode 100644 index 000000000..559e93a27 --- /dev/null +++ b/subprojects/packagefiles/bestsource/meson.build @@ -0,0 +1,28 @@ +project('BestSource', 'cpp', + default_options: ['buildtype=release', 'b_ndebug=if-release', 'cpp_std=c++14'], + meson_version: '>=0.48.0' +) + +libs = [] + +sources = [ + 'src/audiosource.cpp', + 'src/videosource.cpp', + 'src/SrcAttribCache.cpp', + 'src/BSRational.cpp', +] + +deps = [ + dependency('jansson', version: '>= 2.7', required: true), + dependency('libavcodec'), + dependency('libavformat'), + dependency('libavutil'), +] + +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')) +