Redesign AudioProviderFactory
Register functions which create each type of provider rather than the provider types themselves so that the concrete types don't need to be publicly exposed, and use a static list of providers rather than registering them at runtime.
This commit is contained in:
parent
7dd764db6b
commit
938025acb1
20 changed files with 263 additions and 489 deletions
|
@ -117,13 +117,6 @@
|
|||
<ClInclude Include="$(SrcDir)audio_player_oss.h" />
|
||||
<ClInclude Include="$(SrcDir)audio_player_portaudio.h" />
|
||||
<ClInclude Include="$(SrcDir)audio_player_pulse.h" />
|
||||
<ClInclude Include="$(SrcDir)audio_provider_avs.h" />
|
||||
<ClInclude Include="$(SrcDir)audio_provider_convert.h" />
|
||||
<ClInclude Include="$(SrcDir)audio_provider_dummy.h" />
|
||||
<ClInclude Include="$(SrcDir)audio_provider_ffmpegsource.h" />
|
||||
<ClInclude Include="$(SrcDir)audio_provider_hd.h" />
|
||||
<ClInclude Include="$(SrcDir)audio_provider_lock.h" />
|
||||
<ClInclude Include="$(SrcDir)audio_provider_ram.h" />
|
||||
<ClInclude Include="$(SrcDir)audio_renderer.h" />
|
||||
<ClInclude Include="$(SrcDir)audio_renderer_spectrum.h" />
|
||||
<ClInclude Include="$(SrcDir)audio_renderer_waveform.h" />
|
||||
|
|
|
@ -177,24 +177,6 @@
|
|||
<ClInclude Include="$(SrcDir)ass_style_storage.h">
|
||||
<Filter>ASS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="$(SrcDir)audio_provider_ram.h">
|
||||
<Filter>Audio\Providers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="$(SrcDir)audio_provider_avs.h">
|
||||
<Filter>Audio\Providers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="$(SrcDir)audio_provider_convert.h">
|
||||
<Filter>Audio\Providers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="$(SrcDir)audio_provider_dummy.h">
|
||||
<Filter>Audio\Providers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="$(SrcDir)audio_provider_ffmpegsource.h">
|
||||
<Filter>Audio\Providers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="$(SrcDir)audio_provider_hd.h">
|
||||
<Filter>Audio\Providers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="$(SrcDir)audio_box.h">
|
||||
<Filter>Audio\UI</Filter>
|
||||
</ClInclude>
|
||||
|
@ -645,9 +627,6 @@
|
|||
<ClInclude Include="$(SrcDir)scintilla_text_selection_controller.h">
|
||||
<Filter>Main UI\Edit box</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="$(SrcDir)audio_provider_lock.h">
|
||||
<Filter>Audio\Providers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="$(SrcDir)dialog_manager.h">
|
||||
<Filter>Utilities\UI utilities</Filter>
|
||||
</ClInclude>
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
#include "audio_controller.h"
|
||||
|
||||
#include "ass_file.h"
|
||||
#include "audio_provider_dummy.h"
|
||||
#include "audio_timing.h"
|
||||
#include "compat.h"
|
||||
#include "include/aegisub/audio_player.h"
|
||||
|
|
|
@ -34,13 +34,7 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "audio_provider_avs.h"
|
||||
#include "audio_provider_convert.h"
|
||||
#include "audio_provider_dummy.h"
|
||||
#include "audio_provider_ffmpegsource.h"
|
||||
#include "audio_provider_hd.h"
|
||||
#include "audio_provider_lock.h"
|
||||
#include "audio_provider_ram.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include "audio_controller.h"
|
||||
#include "dialog_progress.h"
|
||||
|
@ -53,9 +47,6 @@
|
|||
#include <libaegisub/log.h>
|
||||
#include <libaegisub/util.h>
|
||||
|
||||
// Defined in audio_provider_pcm.cpp
|
||||
std::unique_ptr<AudioProvider> CreatePCMAudioProvider(agi::fs::path const& filename);
|
||||
|
||||
void AudioProvider::GetAudioWithVolume(void *buf, int64_t start, int64_t count, double volume) const {
|
||||
GetAudio(buf, start, count);
|
||||
|
||||
|
@ -110,68 +101,96 @@ void AudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const {
|
|||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioProvider> CreateDummyAudioProvider(agi::fs::path const& filename);
|
||||
std::unique_ptr<AudioProvider> CreatePCMAudioProvider(agi::fs::path const& filename);
|
||||
std::unique_ptr<AudioProvider> CreateAvisynthAudioProvider(agi::fs::path const& filename);
|
||||
std::unique_ptr<AudioProvider> CreateFFmpegSourceAudioProvider(agi::fs::path const& filename);
|
||||
|
||||
std::unique_ptr<AudioProvider> CreateConvertAudioProvider(std::unique_ptr<AudioProvider> source_provider);
|
||||
std::unique_ptr<AudioProvider> CreateLockAudioProvider(std::unique_ptr<AudioProvider> source_provider);
|
||||
std::unique_ptr<AudioProvider> CreateHDAudioProvider(std::unique_ptr<AudioProvider> source_provider, agi::BackgroundRunner *br);
|
||||
std::unique_ptr<AudioProvider> CreateRAMAudioProvider(std::unique_ptr<AudioProvider> source_provider, agi::BackgroundRunner *br);
|
||||
|
||||
namespace {
|
||||
struct provider_creator {
|
||||
using factory_fn = std::unique_ptr<AudioProvider> (*)(agi::fs::path const&);
|
||||
struct factory {
|
||||
const char *name;
|
||||
factory_fn create;
|
||||
bool hidden;
|
||||
};
|
||||
|
||||
const factory providers[] = {
|
||||
{"Dummy", CreateDummyAudioProvider, true},
|
||||
{"PCM", CreatePCMAudioProvider, true},
|
||||
#ifdef WITH_FFMS2
|
||||
{"FFmpegSource", CreateFFmpegSourceAudioProvider, false},
|
||||
#endif
|
||||
#ifdef WITH_AVISYNTH
|
||||
{"Avisynth", CreateAvisynthAudioProvider, false},
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<std::string> AudioProviderFactory::GetClasses() {
|
||||
std::vector<std::string> list;
|
||||
for (auto const& provider : providers) {
|
||||
if (!provider.hidden)
|
||||
list.push_back(provider.name);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioProvider> AudioProviderFactory::GetProvider(agi::fs::path const& filename) {
|
||||
auto preferred = OPT_GET("Audio/Provider")->GetString();
|
||||
std::vector<const factory *> sorted;
|
||||
auto preferred_insertion_point = sorted.end();
|
||||
for (auto const& provider : providers) {
|
||||
if (provider.hidden)
|
||||
sorted.push_back(&provider);
|
||||
else if (preferred_insertion_point == sorted.end()) {
|
||||
sorted.push_back(&provider);
|
||||
preferred_insertion_point = prev(sorted.end());
|
||||
}
|
||||
else if (preferred == provider.name)
|
||||
sorted.insert(preferred_insertion_point, &provider);
|
||||
else
|
||||
sorted.push_back(&provider);
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioProvider> provider;
|
||||
bool found_file = false;
|
||||
bool found_audio = false;
|
||||
std::string msg;
|
||||
|
||||
template<typename Factory>
|
||||
std::unique_ptr<AudioProvider> try_create(std::string const& name, Factory&& create) {
|
||||
for (auto const& factory : sorted) {
|
||||
try {
|
||||
std::unique_ptr<AudioProvider> provider = create();
|
||||
if (provider)
|
||||
LOG_I("audio_provider") << "Using audio provider: " << name;
|
||||
return provider;
|
||||
provider = factory->create(filename);
|
||||
if (!provider) continue;
|
||||
LOG_I("audio_provider") << "Using audio provider: " << factory->name;
|
||||
break;
|
||||
}
|
||||
catch (agi::fs::FileNotFound const& err) {
|
||||
LOG_D("audio_provider") << err.GetChainedMessage();
|
||||
msg += name + ": " + err.GetMessage() + " not found.\n";
|
||||
msg += std::string(factory->name) + ": " + err.GetMessage() + " not found.\n";
|
||||
}
|
||||
catch (agi::AudioDataNotFoundError const& err) {
|
||||
LOG_D("audio_provider") << err.GetChainedMessage();
|
||||
found_file = true;
|
||||
msg += name + ": " + err.GetChainedMessage() + "\n";
|
||||
msg += std::string(factory->name) + ": " + err.GetChainedMessage() + "\n";
|
||||
}
|
||||
catch (agi::AudioOpenError const& err) {
|
||||
LOG_D("audio_provider") << err.GetChainedMessage();
|
||||
found_audio = true;
|
||||
found_file = true;
|
||||
msg += name + ": " + err.GetChainedMessage() + "\n";
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioProvider> AudioProviderFactory::GetProvider(agi::fs::path const& filename) {
|
||||
provider_creator creator;
|
||||
std::unique_ptr<AudioProvider> provider;
|
||||
|
||||
provider = creator.try_create("Dummy audio provider", [&]() {
|
||||
return agi::util::make_unique<DummyAudioProvider>(filename);
|
||||
});
|
||||
|
||||
// Try a PCM provider first
|
||||
if (!provider)
|
||||
provider = creator.try_create("PCM audio provider", [&]() { return CreatePCMAudioProvider(filename); });
|
||||
|
||||
if (!provider) {
|
||||
std::vector<std::string> list = GetClasses(OPT_GET("Audio/Provider")->GetString());
|
||||
if (list.empty()) throw agi::NoAudioProvidersError("No audio providers are available.", nullptr);
|
||||
|
||||
for (auto const& name : list) {
|
||||
provider = creator.try_create(name, [&]() { return Create(name, filename); });
|
||||
if (provider) break;
|
||||
msg += std::string(factory->name) + ": " + err.GetChainedMessage() + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!provider) {
|
||||
if (creator.found_audio)
|
||||
throw agi::AudioProviderOpenError(creator.msg, nullptr);
|
||||
if (creator.found_file)
|
||||
throw agi::AudioDataNotFoundError(creator.msg, nullptr);
|
||||
if (found_audio)
|
||||
throw agi::AudioProviderOpenError(msg, nullptr);
|
||||
if (found_file)
|
||||
throw agi::AudioDataNotFoundError(msg, nullptr);
|
||||
throw agi::fs::FileNotFound(filename);
|
||||
}
|
||||
|
||||
|
@ -184,24 +203,15 @@ std::unique_ptr<AudioProvider> AudioProviderFactory::GetProvider(agi::fs::path c
|
|||
// Change provider to RAM/HD cache if needed
|
||||
int cache = OPT_GET("Audio/Cache/Type")->GetInt();
|
||||
if (!cache || !needsCache)
|
||||
return agi::util::make_unique<LockAudioProvider>(std::move(provider));
|
||||
return CreateLockAudioProvider(std::move(provider));
|
||||
|
||||
DialogProgress progress(wxGetApp().frame, _("Load audio"));
|
||||
|
||||
// Convert to RAM
|
||||
if (cache == 1) return agi::util::make_unique<RAMAudioProvider>(std::move(provider), &progress);
|
||||
if (cache == 1) return CreateRAMAudioProvider(std::move(provider), &progress);
|
||||
|
||||
// Convert to HD
|
||||
if (cache == 2) return agi::util::make_unique<HDAudioProvider>(std::move(provider), &progress);
|
||||
if (cache == 2) return CreateHDAudioProvider(std::move(provider), &progress);
|
||||
|
||||
throw agi::AudioCacheOpenError("Unknown caching method", nullptr);
|
||||
}
|
||||
|
||||
void AudioProviderFactory::RegisterProviders() {
|
||||
#ifdef WITH_AVISYNTH
|
||||
Register<AvisynthAudioProvider>("Avisynth");
|
||||
#endif
|
||||
#ifdef WITH_FFMS2
|
||||
Register<FFmpegSourceAudioProvider>("FFmpegSource");
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -35,8 +35,10 @@
|
|||
#include "config.h"
|
||||
|
||||
#ifdef WITH_AVISYNTH
|
||||
#include "audio_provider_avs.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include "avisynth.h"
|
||||
#include "avisynth_wrap.h"
|
||||
#include "audio_controller.h"
|
||||
#include "options.h"
|
||||
#include "utils.h"
|
||||
|
@ -45,9 +47,24 @@
|
|||
#include <libaegisub/charset_conv.h>
|
||||
#include <libaegisub/fs.h>
|
||||
#include <libaegisub/path.h>
|
||||
#include <libaegisub/util.h>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace {
|
||||
class AvisynthAudioProvider final : public AudioProvider {
|
||||
AviSynthWrapper avs_wrapper;
|
||||
PClip clip;
|
||||
|
||||
void LoadFromClip(AVSValue clip);
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count) const;
|
||||
|
||||
public:
|
||||
AvisynthAudioProvider(agi::fs::path const& filename);
|
||||
|
||||
bool NeedsCache() const override { return true; }
|
||||
};
|
||||
|
||||
AvisynthAudioProvider::AvisynthAudioProvider(agi::fs::path const& filename) {
|
||||
this->filename = filename;
|
||||
|
||||
|
@ -128,4 +145,9 @@ void AvisynthAudioProvider::LoadFromClip(AVSValue clip) {
|
|||
void AvisynthAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const {
|
||||
clip->GetAudio(buf, start, count, avs_wrapper.GetEnv());
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioProvider> CreateAvisynthAudioProvider(agi::fs::path const& file) {
|
||||
return agi::util::make_unique<AvisynthAudioProvider>(file);
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
// Copyright (c) 2005-2006, Rodrigo Braz Monteiro, Fredrik Mellbin
|
||||
// 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/
|
||||
|
||||
/// @file audio_provider_avs.h
|
||||
/// @see audio_provider_avs.cpp
|
||||
/// @ingroup audio_input
|
||||
///
|
||||
|
||||
#ifdef WITH_AVISYNTH
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include "avisynth.h"
|
||||
#include "avisynth_wrap.h"
|
||||
|
||||
class AvisynthAudioProvider final : public AudioProvider {
|
||||
AviSynthWrapper avs_wrapper;
|
||||
PClip clip;
|
||||
|
||||
void LoadFromClip(AVSValue clip);
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count) const;
|
||||
|
||||
public:
|
||||
AvisynthAudioProvider(agi::fs::path const& filename);
|
||||
|
||||
bool NeedsCache() const { return true; }
|
||||
};
|
||||
#endif
|
|
@ -21,10 +21,9 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "audio_provider_convert.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include "audio_controller.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include <libaegisub/log.h>
|
||||
#include <libaegisub/util.h>
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
// Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
|
||||
//
|
||||
// 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_convert.h
|
||||
/// @see audio_provider_convert.cpp
|
||||
/// @ingroup audio_input
|
||||
///
|
||||
|
||||
#include <memory>
|
||||
|
||||
class AudioProvider;
|
||||
|
||||
/// Get an audio provider which supplies audio in a format supported by Aegisub's players
|
||||
std::unique_ptr<AudioProvider> CreateConvertAudioProvider(std::unique_ptr<AudioProvider> source_provider);
|
|
@ -34,10 +34,10 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "audio_provider_dummy.h"
|
||||
#include "utils.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include <libaegisub/fs.h>
|
||||
#include <libaegisub/util.h>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
|
@ -61,25 +61,35 @@
|
|||
* in every channel even if one would be LFE.
|
||||
* "ln", length of signal in samples. ln/sr gives signal length in seconds.
|
||||
*/
|
||||
DummyAudioProvider::DummyAudioProvider(agi::fs::path const& uri) {
|
||||
if (!boost::starts_with(uri.string(), "dummy-audio:"))
|
||||
throw agi::fs::FileNotFound(std::string("Not a dummy audio URI"));
|
||||
|
||||
noise = boost::contains(uri.string(), ":noise?");
|
||||
channels = 1;
|
||||
sample_rate = 44100;
|
||||
bytes_per_sample = 2;
|
||||
float_samples = false;
|
||||
num_samples = (int64_t)5*30*60*1000 * sample_rate / 1000;
|
||||
}
|
||||
|
||||
void DummyAudioProvider::FillBuffer(void *buf, int64_t, int64_t count) const {
|
||||
namespace {
|
||||
class DummyAudioProvider final : public AudioProvider {
|
||||
bool noise;
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count) const override {
|
||||
if (noise) {
|
||||
short *workbuf = (short*)buf;
|
||||
auto workbuf = static_cast<uint16_t *>(buf);
|
||||
while (count-- > 0)
|
||||
*workbuf++ = (rand() - RAND_MAX/2) * 10000 / RAND_MAX;
|
||||
}
|
||||
else {
|
||||
memset(buf, 0, count * bytes_per_sample);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
DummyAudioProvider(agi::fs::path const& uri) {
|
||||
noise = boost::contains(uri.string(), ":noise?");
|
||||
channels = 1;
|
||||
sample_rate = 44100;
|
||||
bytes_per_sample = 2;
|
||||
float_samples = false;
|
||||
num_samples = (int64_t)5*30*60*1000 * sample_rate / 1000;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioProvider> CreateDummyAudioProvider(agi::fs::path const& file) {
|
||||
if (!boost::starts_with(file.string(), "dummy-audio:"))
|
||||
return {};
|
||||
return agi::util::make_unique<DummyAudioProvider>(file);
|
||||
}
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
// Copyright (c) 2006, Niels Martin Hansen
|
||||
// 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/
|
||||
|
||||
/// @file audio_provider_dummy.h
|
||||
/// @see audio_provider_dummy.cpp
|
||||
/// @ingroup audio_input
|
||||
///
|
||||
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
class DummyAudioProvider final : public AudioProvider {
|
||||
bool noise;
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count) const override;
|
||||
|
||||
public:
|
||||
DummyAudioProvider(agi::fs::path const& uri);
|
||||
};
|
|
@ -35,15 +35,37 @@
|
|||
#include "config.h"
|
||||
|
||||
#ifdef WITH_FFMS2
|
||||
#include "audio_provider_ffmpegsource.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include "audio_controller.h"
|
||||
#include "ffmpegsource_common.h"
|
||||
#include "options.h"
|
||||
|
||||
#include <libaegisub/fs.h>
|
||||
#include <libaegisub/util.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace {
|
||||
class FFmpegSourceAudioProvider final : public AudioProvider, FFmpegSourceProvider {
|
||||
/// audio source object
|
||||
agi::scoped_holder<FFMS_AudioSource*, void (FFMS_CC *)(FFMS_AudioSource*)> AudioSource;
|
||||
|
||||
mutable char FFMSErrMsg[1024]; ///< FFMS error message
|
||||
mutable FFMS_ErrorInfo ErrInfo; ///< FFMS error codes/messages
|
||||
|
||||
void LoadAudio(agi::fs::path const& filename);
|
||||
void FillBuffer(void *Buf, int64_t Start, int64_t Count) const override {
|
||||
if (FFMS_GetAudio(AudioSource, Buf, Start, Count, &ErrInfo))
|
||||
throw AudioDecodeError(std::string("Failed to get audio samples: ") + ErrInfo.Buffer);
|
||||
}
|
||||
|
||||
public:
|
||||
FFmpegSourceAudioProvider(agi::fs::path const& filename);
|
||||
|
||||
bool NeedsCache() const override { return true; }
|
||||
};
|
||||
|
||||
/// @brief Constructor
|
||||
/// @param filename The filename to open
|
||||
FFmpegSourceAudioProvider::FFmpegSourceAudioProvider(agi::fs::path const& filename) try
|
||||
|
@ -182,8 +204,10 @@ void FFmpegSourceAudioProvider::LoadAudio(agi::fs::path const& filename) {
|
|||
#endif
|
||||
}
|
||||
|
||||
void FFmpegSourceAudioProvider::FillBuffer(void *Buf, int64_t Start, int64_t Count) const {
|
||||
if (FFMS_GetAudio(AudioSource, Buf, Start, Count, &ErrInfo))
|
||||
throw AudioDecodeError(std::string("Failed to get audio samples: ") + ErrInfo.Buffer);
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioProvider> CreateFFmpegSourceAudioProvider(agi::fs::path const& file) {
|
||||
return agi::util::make_unique<FFmpegSourceAudioProvider>(file);
|
||||
}
|
||||
|
||||
#endif /* WITH_FFMS2 */
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
// Copyright (c) 2008-2009, Karl Blomster
|
||||
// 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/
|
||||
|
||||
/// @file audio_provider_ffmpegsource.h
|
||||
/// @see audio_provider_ffmpegsource.cpp
|
||||
/// @ingroup audio_input ffms
|
||||
///
|
||||
|
||||
#ifdef WITH_FFMS2
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include "ffmpegsource_common.h"
|
||||
|
||||
/// @class FFmpegSourceAudioProvider
|
||||
/// @brief Implements audio loading with the FFMS library.
|
||||
class FFmpegSourceAudioProvider final : public AudioProvider, FFmpegSourceProvider {
|
||||
/// audio source object
|
||||
agi::scoped_holder<FFMS_AudioSource*, void (FFMS_CC *)(FFMS_AudioSource*)> AudioSource;
|
||||
|
||||
mutable char FFMSErrMsg[1024]; ///< FFMS error message
|
||||
mutable FFMS_ErrorInfo ErrInfo; ///< FFMS error codes/messages
|
||||
|
||||
void LoadAudio(agi::fs::path const& filename);
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count) const override;
|
||||
|
||||
public:
|
||||
FFmpegSourceAudioProvider(agi::fs::path const& filename);
|
||||
|
||||
bool NeedsCache() const override { return true; }
|
||||
};
|
||||
#endif
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "audio_provider_hd.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include "audio_controller.h"
|
||||
#include "compat.h"
|
||||
|
@ -33,10 +33,22 @@
|
|||
#include <boost/filesystem.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/interprocess/detail/os_thread_functions.hpp>
|
||||
#include <wx/intl.h>
|
||||
|
||||
HDAudioProvider::HDAudioProvider(std::unique_ptr<AudioProvider> src, agi::BackgroundRunner *br)
|
||||
: AudioProviderWrapper(std::move(src))
|
||||
{
|
||||
namespace {
|
||||
class HDAudioProvider final : public AudioProviderWrapper {
|
||||
std::unique_ptr<agi::temp_file_mapping> file;
|
||||
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count) const override {
|
||||
start *= channels * bytes_per_sample;
|
||||
count *= channels * bytes_per_sample;
|
||||
memcpy(buf, file->read(start, count), count);
|
||||
}
|
||||
|
||||
public:
|
||||
HDAudioProvider(std::unique_ptr<AudioProvider> src, agi::BackgroundRunner *br)
|
||||
: AudioProviderWrapper(std::move(src))
|
||||
{
|
||||
auto path = OPT_GET("Audio/Cache/HD/Location")->GetString();
|
||||
if (path == "default")
|
||||
path = "?temp";
|
||||
|
@ -64,12 +76,10 @@ HDAudioProvider::HDAudioProvider(std::unique_ptr<AudioProvider> src, agi::Backgr
|
|||
if (ps->IsCancelled()) return;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
HDAudioProvider::~HDAudioProvider() { }
|
||||
|
||||
void HDAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const {
|
||||
start *= channels * bytes_per_sample;
|
||||
count *= channels * bytes_per_sample;
|
||||
memcpy(buf, file->read(start, count), count);
|
||||
std::unique_ptr<AudioProvider> CreateHDAudioProvider(std::unique_ptr<AudioProvider> src, agi::BackgroundRunner *br) {
|
||||
return agi::util::make_unique<HDAudioProvider>(std::move(src), br);
|
||||
}
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
|
||||
//
|
||||
// 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/
|
||||
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
namespace agi {
|
||||
class BackgroundRunner;
|
||||
class temp_file_mapping;
|
||||
}
|
||||
|
||||
class HDAudioProvider final : public AudioProviderWrapper {
|
||||
std::unique_ptr<agi::temp_file_mapping> file;
|
||||
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count) const override;
|
||||
|
||||
public:
|
||||
HDAudioProvider(std::unique_ptr<AudioProvider> source, agi::BackgroundRunner *br);
|
||||
~HDAudioProvider();
|
||||
};
|
|
@ -18,14 +18,30 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "audio_provider_lock.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
LockAudioProvider::LockAudioProvider(std::unique_ptr<AudioProvider> src)
|
||||
: AudioProviderWrapper(std::move(src))
|
||||
{
|
||||
}
|
||||
#include <libaegisub/util.h>
|
||||
|
||||
void LockAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const {
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace {
|
||||
class LockAudioProvider final : public AudioProviderWrapper {
|
||||
mutable std::mutex mutex;
|
||||
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count) const override {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
source->GetAudio(buf, start, count);
|
||||
}
|
||||
|
||||
public:
|
||||
LockAudioProvider(std::unique_ptr<AudioProvider> src)
|
||||
: AudioProviderWrapper(std::move(src))
|
||||
{
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioProvider> CreateLockAudioProvider(std::unique_ptr<AudioProvider> src) {
|
||||
return agi::util::make_unique<LockAudioProvider>(std::move(src));
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
// Copyright (c) 2012, Thomas Goyne <plorkyeran@aegisub.org>
|
||||
//
|
||||
// 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.
|
||||
|
||||
/// @file audio_provider_lock.h
|
||||
/// @brief An audio provider adapter for un-threadsafe audio providers
|
||||
/// @ingroup audio_input
|
||||
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
class LockAudioProvider final : public AudioProviderWrapper {
|
||||
mutable std::mutex mutex;
|
||||
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count) const override;
|
||||
public:
|
||||
LockAudioProvider(std::unique_ptr<AudioProvider> source);
|
||||
};
|
|
@ -34,21 +34,36 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "audio_provider_ram.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include "audio_controller.h"
|
||||
#include "compat.h"
|
||||
#include "options.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <libaegisub/background_runner.h>
|
||||
#include <libaegisub/util.h>
|
||||
|
||||
#include <array>
|
||||
#include <boost/container/stable_vector.hpp>
|
||||
#include <wx/intl.h>
|
||||
|
||||
namespace {
|
||||
|
||||
#define CacheBits 22
|
||||
#define CacheBlockSize (1 << CacheBits)
|
||||
|
||||
RAMAudioProvider::RAMAudioProvider(std::unique_ptr<AudioProvider> src, agi::BackgroundRunner *br)
|
||||
: AudioProviderWrapper(std::move(src))
|
||||
{
|
||||
class RAMAudioProvider final : public AudioProviderWrapper {
|
||||
#ifdef _MSC_VER
|
||||
boost::container::stable_vector<char[1 << 22]> blockcache;
|
||||
#else
|
||||
boost::container::stable_vector<std::array<char, 1 << 22>> blockcache;
|
||||
#endif
|
||||
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count) const override;
|
||||
|
||||
public:
|
||||
RAMAudioProvider(std::unique_ptr<AudioProvider> src, agi::BackgroundRunner *br)
|
||||
: AudioProviderWrapper(std::move(src))
|
||||
{
|
||||
try {
|
||||
blockcache.resize((source->GetNumSamples() * source->GetBytesPerSample() + CacheBlockSize - 1) >> CacheBits);
|
||||
}
|
||||
|
@ -56,22 +71,18 @@ RAMAudioProvider::RAMAudioProvider(std::unique_ptr<AudioProvider> src, agi::Back
|
|||
throw agi::AudioCacheOpenError("Couldn't open audio, not enough ram available.", nullptr);
|
||||
}
|
||||
|
||||
br->Run(std::bind(&RAMAudioProvider::FillCache, this, source.get(), std::placeholders::_1));
|
||||
}
|
||||
|
||||
void RAMAudioProvider::FillCache(AudioProvider *source, agi::ProgressSink *ps) {
|
||||
br->Run([&](agi::ProgressSink *ps) {
|
||||
ps->SetMessage(from_wx(_("Reading into RAM")));
|
||||
|
||||
int64_t readsize = CacheBlockSize / source->GetBytesPerSample();
|
||||
for (size_t i = 0; i < blockcache.size(); i++) {
|
||||
if (ps->IsCancelled()) return;
|
||||
ps->SetProgress(i + 1, blockcache.size());
|
||||
source->GetAudio(&blockcache[i][0], i * readsize, std::min<int64_t>(readsize, num_samples - i * readsize));
|
||||
|
||||
if (ps->IsCancelled()) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void RAMAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const {
|
||||
char *charbuf = static_cast<char *>(buf);
|
||||
|
@ -90,3 +101,8 @@ void RAMAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const
|
|||
bytesremaining -= readsize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioProvider> CreateRAMAudioProvider(std::unique_ptr<AudioProvider> src, agi::BackgroundRunner *br) {
|
||||
return agi::util::make_unique<RAMAudioProvider>(std::move(src), br);
|
||||
}
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
// Copyright (c) 2006, Rodrigo Braz Monteiro
|
||||
// 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/
|
||||
|
||||
/// @file audio_provider_ram.h
|
||||
/// @see audio_provider_ram.cpp
|
||||
/// @ingroup audio_input
|
||||
///
|
||||
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include <array>
|
||||
#include <boost/container/stable_vector.hpp>
|
||||
|
||||
namespace agi {
|
||||
class BackgroundRunner;
|
||||
class ProgressSink;
|
||||
}
|
||||
|
||||
class RAMAudioProvider final : public AudioProviderWrapper {
|
||||
#ifdef _MSC_VER
|
||||
boost::container::stable_vector<char[1 << 22]> blockcache;
|
||||
#else
|
||||
boost::container::stable_vector<std::array<char, 1 << 22>> blockcache;
|
||||
#endif
|
||||
|
||||
void FillCache(AudioProvider *source, agi::ProgressSink *ps);
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count) const override;
|
||||
|
||||
public:
|
||||
RAMAudioProvider(std::unique_ptr<AudioProvider> source, agi::BackgroundRunner *br);
|
||||
};
|
|
@ -34,8 +34,6 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "factory_manager.h"
|
||||
|
||||
#include <libaegisub/exception.h>
|
||||
#include <libaegisub/fs_fwd.h>
|
||||
|
||||
|
@ -90,9 +88,8 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class AudioProviderFactory final : public Factory<AudioProvider, agi::fs::path> {
|
||||
public:
|
||||
static void RegisterProviders();
|
||||
struct AudioProviderFactory {
|
||||
static std::vector<std::string> GetClasses();
|
||||
|
||||
/// Get a provider for the file
|
||||
/// @param filename URI to open
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
#include "config.h"
|
||||
|
||||
#include "include/aegisub/audio_player.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
#include "include/aegisub/spellchecker.h"
|
||||
#include "include/aegisub/subtitles_provider.h"
|
||||
#include "plugin_manager.h"
|
||||
|
@ -46,7 +45,6 @@
|
|||
|
||||
void RegisterBuiltInPlugins() {
|
||||
VideoProviderFactory::RegisterProviders();
|
||||
AudioProviderFactory::RegisterProviders();
|
||||
AudioPlayerFactory::RegisterProviders();
|
||||
SubtitlesProviderFactory::RegisterProviders();
|
||||
SpellCheckerFactory::RegisterProviders();
|
||||
|
|
Loading…
Reference in a new issue