bestsource: Add audio provider

This commit is contained in:
arch1t3cht 2022-08-11 02:05:16 +02:00
parent 70fd08aabe
commit 6af0b41f47
10 changed files with 220 additions and 25 deletions

View file

@ -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

View file

@ -0,0 +1,118 @@
// 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");
}
// Taken from BestSource code and reversed
template<typename T>
static void PackChannels(const uint8_t *Src, void *Dst, size_t Length, size_t Channels) {
const T *S = reinterpret_cast<const T *>(Src);
T *D = reinterpret_cast<T *>(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<uint8_t> unpacked_buf(channels * bytes_per_sample * Count);
std::vector<uint8_t *> bufs(channels);
for (int i = 0; i < channels; i++) {
bufs[i] = unpacked_buf.data() + i * bytes_per_sample * Count;
}
const_cast<BestAudioSource &>(bs).GetAudio(bufs.data(), Start, Count);
if (bytes_per_sample == 1)
PackChannels<uint8_t>(unpacked_buf.data(), Buf, Count, channels);
else if (bytes_per_sample == 2)
PackChannels<uint16_t>(unpacked_buf.data(), Buf, Count, channels);
else if (bytes_per_sample == 4)
PackChannels<uint32_t>(unpacked_buf.data(), Buf, Count, channels);
else if (bytes_per_sample == 8)
PackChannels<uint64_t>(unpacked_buf.data(), Buf, Count, channels);
}
}
std::unique_ptr<agi::AudioProvider> CreateBSAudioProvider(agi::fs::path const& file, agi::BackgroundRunner *br) {
return agi::make_unique<BSAudioProvider>(file, br);
}
#endif /* WITH_BESTSOURCE */

View file

@ -31,6 +31,7 @@ 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 *);
namespace {
struct factory {
@ -48,6 +49,9 @@ const factory providers[] = {
#ifdef WITH_AVISYNTH
{"Avisynth", CreateAvisynthAudioProvider, false},
#endif
#ifdef WITH_BESTSOURCE
{"BestSource", CreateBSAudioProvider, false},
#endif
};
}

49
src/bestsource_common.cpp Normal file
View file

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

28
src/bestsource_common.h Normal file
View file

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

View file

@ -332,6 +332,10 @@
},
"FFmpegSource" : {
"Decode Error Handling" : "ignore"
},
"BestSource": {
"Max Cache Size" : 100,
"Aegisub Cache" : true
}
},
"Avisynth" : {

View file

@ -332,6 +332,10 @@
},
"FFmpegSource" : {
"Decode Error Handling" : "ignore"
},
"BestSource": {
"Max Cache Size" : 100,
"Aegisub Cache" : true
}
},
"Avisynth" : {

View file

@ -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'],
]

View file

@ -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");

View file

@ -32,7 +32,7 @@ extern "C" {
#include <libswscale/swscale.h>
}
/* #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 <libaegisub/background_runner.h>
#include <libaegisub/log.h>
#include <boost/crc.hpp>
#include <boost/filesystem/path.hpp>
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<BestVideoFrame> bsframe(bs.GetFrame(n));
if (bsframe == nullptr) {