diff --git a/meson.build b/meson.build index 6115d1329..21293e1b9 100644 --- a/meson.build +++ b/meson.build @@ -226,6 +226,7 @@ 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 diff --git a/src/audio_provider_bestsource.cpp b/src/audio_provider_bestsource.cpp new file mode 100644 index 000000000..7229df827 --- /dev/null +++ b/src/audio_provider_bestsource.cpp @@ -0,0 +1,118 @@ +// 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 audio_provider_bestsource.cpp +/// @brief BS-based audio provider +/// @ingroup audio_input bestsource +/// + +#ifdef WITH_BESTSOURCE +#include + +#include "audiosource.h" + +#include "bestsource_common.h" +#include "compat.h" +#include "options.h" + +#include +#include +#include +#include + +#include + +namespace { +class BSAudioProvider final : public agi::AudioProvider { + std::map 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"); +} + +// Taken from BestSource code and reversed +template +static void PackChannels(const uint8_t *Src, void *Dst, size_t Length, size_t Channels) { + const T *S = reinterpret_cast(Src); + T *D = reinterpret_cast(Dst); + for (size_t i = 0; i < Length; i++) { + for (size_t c = 0; c < Channels; c++) + D[c] = S[Length * c]; + S += 1; + D += Channels; + } +} + +void BSAudioProvider::FillBuffer(void *Buf, int64_t Start, int64_t Count) const { + // BS unpacked the channels, so until it gets a feature to disable that, let's just + // pack them in the same way they were unpacked + std::vector unpacked_buf(channels * bytes_per_sample * Count); + std::vector bufs(channels); + for (int i = 0; i < channels; i++) { + bufs[i] = unpacked_buf.data() + i * bytes_per_sample * Count; + } + const_cast(bs).GetAudio(bufs.data(), Start, Count); + + if (bytes_per_sample == 1) + PackChannels(unpacked_buf.data(), Buf, Count, channels); + else if (bytes_per_sample == 2) + PackChannels(unpacked_buf.data(), Buf, Count, channels); + else if (bytes_per_sample == 4) + PackChannels(unpacked_buf.data(), Buf, Count, channels); + else if (bytes_per_sample == 8) + PackChannels(unpacked_buf.data(), Buf, Count, channels); +} + +} + +std::unique_ptr CreateBSAudioProvider(agi::fs::path const& file, agi::BackgroundRunner *br) { + return agi::make_unique(file, br); +} + +#endif /* WITH_BESTSOURCE */ + diff --git a/src/audio_provider_factory.cpp b/src/audio_provider_factory.cpp index 887783644..d0508aa6f 100644 --- a/src/audio_provider_factory.cpp +++ b/src/audio_provider_factory.cpp @@ -31,6 +31,7 @@ using namespace agi; std::unique_ptr CreateAvisynthAudioProvider(fs::path const& filename, BackgroundRunner *); std::unique_ptr CreateFFmpegSourceAudioProvider(fs::path const& filename, BackgroundRunner *); +std::unique_ptr CreateBSAudioProvider(fs::path const& filename, BackgroundRunner *); namespace { struct factory { @@ -48,6 +49,9 @@ const factory providers[] = { #ifdef WITH_AVISYNTH {"Avisynth", CreateAvisynthAudioProvider, false}, #endif +#ifdef WITH_BESTSOURCE + {"BestSource", CreateBSAudioProvider, false}, +#endif }; } diff --git a/src/bestsource_common.cpp b/src/bestsource_common.cpp new file mode 100644 index 000000000..69d627015 --- /dev/null +++ b/src/bestsource_common.cpp @@ -0,0 +1,49 @@ +// 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 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 +#include + +#include +#include + + +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 diff --git a/src/bestsource_common.h b/src/bestsource_common.h new file mode 100644 index 000000000..b93ccc815 --- /dev/null +++ b/src/bestsource_common.h @@ -0,0 +1,28 @@ +// 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 ffmpegsource_common.h +/// @see ffmpegsource_common.cpp +/// @ingroup video_input audio_input ffms +/// + +#ifdef WITH_BESTSOURCE + +#include + +std::string GetBSCacheFile(agi::fs::path const& filename); + +#endif /* WITH_BESTSOURCE */ diff --git a/src/libresrc/default_config.json b/src/libresrc/default_config.json index aeac73b4a..a530abb17 100644 --- a/src/libresrc/default_config.json +++ b/src/libresrc/default_config.json @@ -332,6 +332,10 @@ }, "FFmpegSource" : { "Decode Error Handling" : "ignore" + }, + "BestSource": { + "Max Cache Size" : 100, + "Aegisub Cache" : true } }, "Avisynth" : { diff --git a/src/libresrc/osx/default_config.json b/src/libresrc/osx/default_config.json index a774470fc..a857a6bf0 100644 --- a/src/libresrc/osx/default_config.json +++ b/src/libresrc/osx/default_config.json @@ -332,6 +332,10 @@ }, "FFmpegSource" : { "Decode Error Handling" : "ignore" + }, + "BestSource": { + "Max Cache Size" : 100, + "Aegisub Cache" : true } }, "Avisynth" : { diff --git a/src/meson.build b/src/meson.build index 2d5145dbf..51a5753cd 100644 --- a/src/meson.build +++ b/src/meson.build @@ -224,10 +224,6 @@ 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'], @@ -240,6 +236,9 @@ opt_src = [ ['FFMS2', ['audio_provider_ffmpegsource.cpp', 'video_provider_ffmpegsource.cpp', 'ffmpegsource_common.cpp']], + ['BestSource', ['audio_provider_bestsource.cpp', + 'video_provider_bestsource.cpp', + 'bestsource_common.cpp']], ['Hunspell', 'spellchecker_hunspell.cpp'], ] diff --git a/src/preferences.cpp b/src/preferences.cpp index e0d064c03..1945055e0 100644 --- a/src/preferences.cpp +++ b/src/preferences.cpp @@ -403,6 +403,13 @@ void Advanced_Audio(wxTreebook *book, Preferences *parent) { p->OptionAdd(ffms, _("Always index all audio tracks"), "Provider/FFmpegSource/Index All Tracks"); #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"); diff --git a/src/video_provider_bestsource.cpp b/src/video_provider_bestsource.cpp index c6143a0ec..3b5e0834c 100644 --- a/src/video_provider_bestsource.cpp +++ b/src/video_provider_bestsource.cpp @@ -32,7 +32,7 @@ extern "C" { #include } -/* #include "utils.h" */ +#include "bestsource_common.h" #include "options.h" #include "compat.h" #include "video_frame.h" @@ -44,9 +44,6 @@ namespace agi { class BackgroundRunner; } #include #include -#include -#include - namespace { /// @class BSVideoProvider @@ -61,8 +58,6 @@ class BSVideoProvider final : public VideoProvider { std::string colorspace; bool has_audio = false; - std::string GetCacheFile(agi::fs::path const& filename); - public: BSVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br); @@ -108,7 +103,7 @@ std::string colormatrix_description(const AVFrame *frame) { 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(), GetCacheFile(filename), &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()); @@ -171,20 +166,6 @@ 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) {