forked from mia/Aegisub
Move some of the audio provider machinery to libaegisub
And add tests.
This commit is contained in:
parent
e942a7f0f7
commit
585e9489d9
46 changed files with 1272 additions and 1074 deletions
|
@ -1,5 +1,5 @@
|
|||
ifneq (yes, $(INCLUDING_CHILD_MAKEFILES))
|
||||
COMMANDS := all install clean distclean test depclean osx-bundle osx-dmg test-automation
|
||||
COMMANDS := all install clean distclean test depclean osx-bundle osx-dmg test-automation test-libaegisub
|
||||
.PHONY: $(COMMANDS)
|
||||
.DEFAULT_GOAL := all
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ aegisub_OBJ := \
|
|||
$(d)common/parser.o \
|
||||
$(d)ass/dialogue_parser.o \
|
||||
$(d)ass/time.o \
|
||||
$(subst .cpp,.o,$(wildcard $(d)audio/*.cpp)) \
|
||||
$(subst .cpp,.o,$(wildcard $(d)common/cajun/*.cpp)) \
|
||||
$(subst .cpp,.o,$(wildcard $(d)lua/modules/*.cpp)) \
|
||||
$(subst .c,.o,$(wildcard $(d)lua/modules/*.c)) \
|
||||
|
|
137
libaegisub/audio/provider.cpp
Normal file
137
libaegisub/audio/provider.cpp
Normal file
|
@ -0,0 +1,137 @@
|
|||
// 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 "libaegisub/audio/provider.h"
|
||||
|
||||
#include "libaegisub/fs.h"
|
||||
#include "libaegisub/io.h"
|
||||
#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);
|
||||
|
||||
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)
|
||||
buffer[i] = util::mid<int>(-0x8000, buffer[i] * volume + 0.5, 0x7FFF);
|
||||
}
|
||||
|
||||
void AudioProvider::ZeroFill(void *buf, int64_t count) const {
|
||||
if (bytes_per_sample == 1)
|
||||
// 8 bit formats are usually unsigned with bias 127
|
||||
memset(buf, 127, count * channels);
|
||||
else // While everything else is signed
|
||||
memset(buf, 0, count * bytes_per_sample * channels);
|
||||
}
|
||||
|
||||
void AudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const {
|
||||
if (start < 0) {
|
||||
ZeroFill(buf, std::min(-start, count));
|
||||
buf = static_cast<char *>(buf) + -start * bytes_per_sample * channels;
|
||||
count += start;
|
||||
start = 0;
|
||||
}
|
||||
|
||||
if (start + count > num_samples) {
|
||||
int64_t zero_count = std::min(count, start + count - num_samples);
|
||||
count -= zero_count;
|
||||
ZeroFill(static_cast<char *>(buf) + count * bytes_per_sample * channels, zero_count);
|
||||
}
|
||||
|
||||
if (count <= 0) return;
|
||||
|
||||
try {
|
||||
FillBuffer(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();
|
||||
ZeroFill(buf, count);
|
||||
return;
|
||||
}
|
||||
catch (...) {
|
||||
LOG_E("audio_provider") << "Unknown audio decoding error";
|
||||
ZeroFill(buf, count);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
class writer {
|
||||
io::Save outfile;
|
||||
std::ostream& out;
|
||||
|
||||
public:
|
||||
writer(agi::fs::path const& filename) : outfile(filename, true), out(outfile.Get()) { }
|
||||
|
||||
template<int N>
|
||||
void write(const char(&str)[N]) {
|
||||
out.write(str, N - 1);
|
||||
}
|
||||
|
||||
void write(std::vector<char> const& data) {
|
||||
out.write(data.data(), data.size());
|
||||
}
|
||||
|
||||
template<typename Dest, typename Src>
|
||||
void write(Src v) {
|
||||
auto converted = static_cast<Dest>(v);
|
||||
out.write(reinterpret_cast<char *>(&converted), sizeof(Dest));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void SaveAudioClip(AudioProvider *provider, fs::path const& path, int start_time, int end_time) {
|
||||
auto start_sample = ((int64_t)start_time * provider->GetSampleRate() + 999) / 1000;
|
||||
auto end_sample = ((int64_t)end_time * provider->GetSampleRate() + 999) / 1000;
|
||||
if (start_sample >= provider->GetNumSamples() || start_sample >= end_sample) return;
|
||||
|
||||
size_t bytes_per_sample = provider->GetBytesPerSample() * provider->GetChannels();
|
||||
size_t bufsize = (end_sample - start_sample) * bytes_per_sample;
|
||||
|
||||
writer out{path};
|
||||
out.write("RIFF");
|
||||
out.write<int32_t>(bufsize + 36);
|
||||
|
||||
out.write("WAVEfmt ");
|
||||
out.write<int32_t>(16); // Size of chunk
|
||||
out.write<int16_t>(1); // compression format (PCM)
|
||||
out.write<int16_t>(provider->GetChannels());
|
||||
out.write<int32_t>(provider->GetSampleRate());
|
||||
out.write<int32_t>(provider->GetSampleRate() * provider->GetChannels() * provider->GetBytesPerSample());
|
||||
out.write<int16_t>(provider->GetChannels() * provider->GetBytesPerSample());
|
||||
out.write<int16_t>(provider->GetBytesPerSample() * 8);
|
||||
|
||||
out.write("data");
|
||||
out.write<int32_t>(bufsize);
|
||||
|
||||
// samples per read
|
||||
size_t spr = 65536 / bytes_per_sample;
|
||||
std::vector<char> buf;
|
||||
for (int64_t i = start_sample; i < end_sample; i += spr) {
|
||||
spr = std::min<size_t>(spr, end_sample - i);
|
||||
buf.resize(spr * bytes_per_sample);
|
||||
provider->GetAudio(&buf[0], i, spr);
|
||||
out.write(buf);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
|
||||
// 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
|
||||
|
@ -14,57 +14,55 @@
|
|||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
|
||||
/// @file audio_provider_convert.cpp
|
||||
/// @brief Intermediate sample format-converting audio provider
|
||||
/// @ingroup audio_input
|
||||
///
|
||||
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include "audio_controller.h"
|
||||
#include "libaegisub/audio/provider.h"
|
||||
|
||||
#include <libaegisub/log.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
#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;
|
||||
public:
|
||||
BitdepthConvertAudioProvider(std::unique_ptr<AudioProvider> src) : AudioProviderWrapper(std::move(src)) {
|
||||
if (bytes_per_sample > 8)
|
||||
throw agi::AudioProviderOpenError("Audio format converter: audio with bitdepths greater than 64 bits/sample is currently unsupported");
|
||||
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 count) const override {
|
||||
std::vector<char> src_buf(count * src_bytes_per_sample * channels);
|
||||
source->GetAudio(&src_buf[0], start, count);
|
||||
std::vector<uint8_t> src_buf(count * src_bytes_per_sample * channels);
|
||||
source->GetAudio(src_buf.data(), start, count);
|
||||
|
||||
int16_t *dest = reinterpret_cast<int16_t*>(buf);
|
||||
|
||||
for (int64_t i = 0; i < count * channels; ++i) {
|
||||
int64_t sample = 0;
|
||||
char *sample_ptr = (char*)&sample;
|
||||
char *src = &src_buf[i * src_bytes_per_sample];
|
||||
|
||||
// 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_ptr = static_cast<uint8_t>(*src) - 127;
|
||||
else
|
||||
memcpy(sample_ptr, src, src_bytes_per_sample);
|
||||
sample = src_buf[i] - 127;
|
||||
else {
|
||||
for (int j = 0; j < src_bytes_per_sample; ++j) {
|
||||
sample <<= 8;
|
||||
sample += src_buf[i * src_bytes_per_sample + j];
|
||||
}
|
||||
}
|
||||
|
||||
if (static_cast<size_t>(src_bytes_per_sample) > sizeof(Target))
|
||||
sample >>= (src_bytes_per_sample - sizeof(Target)) * 8;
|
||||
else if (static_cast<size_t>(src_bytes_per_sample) < sizeof(Target))
|
||||
sample <<= (sizeof(Target) - src_bytes_per_sample ) * 8;
|
||||
|
||||
dest[i] = (Target)sample;
|
||||
dest[i] = static_cast<Target>(sample);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -82,7 +80,7 @@ public:
|
|||
std::vector<Source> src_buf(count * channels);
|
||||
source->GetAudio(&src_buf[0], start, count);
|
||||
|
||||
Target *dest = reinterpret_cast<Target*>(buf);
|
||||
auto dest = reinterpret_cast<Target*>(buf);
|
||||
|
||||
for (size_t i = 0; i < static_cast<size_t>(count * channels); ++i) {
|
||||
Source expanded;
|
||||
|
@ -107,9 +105,9 @@ class DownmixAudioProvider final : public AudioProviderWrapper {
|
|||
public:
|
||||
DownmixAudioProvider(std::unique_ptr<AudioProvider> src) : AudioProviderWrapper(std::move(src)) {
|
||||
if (bytes_per_sample != 2)
|
||||
throw agi::InternalError("DownmixAudioProvider requires 16-bit input");
|
||||
throw InternalError("DownmixAudioProvider requires 16-bit input");
|
||||
if (channels == 1)
|
||||
throw agi::InternalError("DownmixAudioProvider requires multi-channel input");
|
||||
throw InternalError("DownmixAudioProvider requires multi-channel input");
|
||||
src_channels = channels;
|
||||
channels = 1;
|
||||
}
|
||||
|
@ -137,9 +135,9 @@ class SampleDoublingAudioProvider final : public AudioProviderWrapper {
|
|||
public:
|
||||
SampleDoublingAudioProvider(std::unique_ptr<AudioProvider> src) : AudioProviderWrapper(std::move(src)) {
|
||||
if (source->GetBytesPerSample() != 2)
|
||||
throw agi::InternalError("UpsampleAudioProvider requires 16-bit input");
|
||||
throw InternalError("UpsampleAudioProvider requires 16-bit input");
|
||||
if (source->GetChannels() != 1)
|
||||
throw agi::InternalError("UpsampleAudioProvider requires mono input");
|
||||
throw InternalError("UpsampleAudioProvider requires mono input");
|
||||
|
||||
sample_rate *= 2;
|
||||
num_samples *= 2;
|
||||
|
@ -149,11 +147,11 @@ public:
|
|||
void FillBuffer(void *buf, int64_t start, int64_t count) const override {
|
||||
if (count == 0) return;
|
||||
|
||||
int not_end = start + count < num_samples;
|
||||
bool not_end = start + count < num_samples;
|
||||
int64_t src_count = count / 2;
|
||||
source->GetAudio(buf, start / 2, src_count + not_end);
|
||||
|
||||
int16_t *buf16 = reinterpret_cast<int16_t*>(buf);
|
||||
auto buf16 = reinterpret_cast<int16_t*>(buf);
|
||||
|
||||
if (!not_end) {
|
||||
// We weren't able to request a sample past the end so just
|
||||
|
@ -171,7 +169,9 @@ public:
|
|||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace agi {
|
||||
std::unique_ptr<AudioProvider> CreateConvertAudioProvider(std::unique_ptr<AudioProvider> provider) {
|
||||
// Ensure 16-bit audio with proper endianness
|
||||
if (provider->AreSamplesFloat()) {
|
||||
|
@ -200,3 +200,4 @@ std::unique_ptr<AudioProvider> CreateConvertAudioProvider(std::unique_ptr<AudioP
|
|||
|
||||
return provider;
|
||||
}
|
||||
}
|
80
libaegisub/audio/provider_dummy.cpp
Normal file
80
libaegisub/audio/provider_dummy.cpp
Normal file
|
@ -0,0 +1,80 @@
|
|||
// 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 "libaegisub/audio/provider.h"
|
||||
|
||||
#include "libaegisub/fs.h"
|
||||
#include "libaegisub/make_unique.h"
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <random>
|
||||
|
||||
/*
|
||||
* scheme ::= "dummy-audio" ":" signal-specifier "?" signal-parameters
|
||||
* signal-specifier ::= "silence" | "noise" | "sine" "/" frequency
|
||||
* frequency ::= integer
|
||||
* signal-parameters ::= signal-parameter [ "&" signal-parameters ]
|
||||
* signal-parameter ::= signal-parameter-name "=" integer
|
||||
* signal-parameter-name ::= "sr" | "bd" | "ch" | "ln"
|
||||
*
|
||||
* Signal types:
|
||||
* "silence", a silent signal is generated.
|
||||
* "noise", a white noise signal is generated.
|
||||
* "sine", a sine wave is generated at the specified frequency.
|
||||
*
|
||||
* Signal parameters:
|
||||
* "sr", sample rate to generate signal at.
|
||||
* "bd", bit depth to generate signal at (usually 16).
|
||||
* "ch", number of channels to generate, usually 1 or 2. The same signal is generated
|
||||
* in every channel even if one would be LFE.
|
||||
* "ln", length of signal in samples. ln/sr gives signal length in seconds.
|
||||
*/
|
||||
|
||||
namespace {
|
||||
using namespace agi;
|
||||
class DummyAudioProvider final : public AudioProvider {
|
||||
bool noise;
|
||||
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count) const override {
|
||||
if (noise) {
|
||||
std::default_random_engine e;
|
||||
std::uniform_int_distribution<int16_t> uniform_dist(-5000, 5000);
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
static_cast<short *>(buf)[i] = uniform_dist(e);
|
||||
}
|
||||
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;
|
||||
decoded_samples = num_samples = (int64_t)5*30*60*1000 * sample_rate / 1000;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace agi {
|
||||
std::unique_ptr<AudioProvider> CreateDummyAudioProvider(agi::fs::path const& file, agi::BackgroundRunner *) {
|
||||
if (!boost::starts_with(file.string(), "dummy-audio:"))
|
||||
return {};
|
||||
return agi::make_unique<DummyAudioProvider>(file);
|
||||
}
|
||||
}
|
|
@ -14,10 +14,7 @@
|
|||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include "audio_controller.h"
|
||||
#include "options.h"
|
||||
#include "libaegisub/audio/provider.h"
|
||||
|
||||
#include <libaegisub/file_mapping.h>
|
||||
#include <libaegisub/format.h>
|
||||
|
@ -27,11 +24,14 @@
|
|||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/interprocess/detail/os_thread_functions.hpp>
|
||||
#include <ctime>
|
||||
#include <thread>
|
||||
|
||||
namespace {
|
||||
using namespace agi;
|
||||
|
||||
class HDAudioProvider final : public AudioProviderWrapper {
|
||||
std::unique_ptr<agi::temp_file_mapping> file;
|
||||
mutable temp_file_mapping file;
|
||||
std::atomic<bool> cancelled = {false};
|
||||
std::thread decoder;
|
||||
|
||||
|
@ -45,35 +45,31 @@ class HDAudioProvider final : public AudioProviderWrapper {
|
|||
if (count > 0) {
|
||||
start *= bytes_per_sample;
|
||||
count *= bytes_per_sample;
|
||||
memcpy(buf, file->read(start, count), count);
|
||||
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))
|
||||
throw AudioProviderError("Not enough free disk space in " + dir.string() + " to cache the audio");
|
||||
|
||||
return format("audio-%lld-%lld", time(nullptr),
|
||||
boost::interprocess::ipcdetail::get_current_process_id());
|
||||
}
|
||||
|
||||
public:
|
||||
HDAudioProvider(std::unique_ptr<AudioProvider> src)
|
||||
HDAudioProvider(std::unique_ptr<AudioProvider> src, agi::fs::path const& dir)
|
||||
: AudioProviderWrapper(std::move(src))
|
||||
, file(dir / CacheFilename(dir), num_samples * bytes_per_sample)
|
||||
{
|
||||
decoded_samples = 0;
|
||||
|
||||
auto path = OPT_GET("Audio/Cache/HD/Location")->GetString();
|
||||
if (path == "default")
|
||||
path = "?temp";
|
||||
auto cache_dir = config::path->MakeAbsolute(config::path->Decode(path), "?temp");
|
||||
|
||||
// Check free space
|
||||
if ((uint64_t)num_samples * bytes_per_sample > agi::fs::FreeSpace(cache_dir))
|
||||
throw agi::AudioCacheOpenError("Not enough free disk space in " + cache_dir.string() + " to cache the audio");
|
||||
|
||||
auto filename = agi::format("audio-%lld-%lld", time(nullptr),
|
||||
boost::interprocess::ipcdetail::get_current_process_id());
|
||||
|
||||
file = agi::make_unique<agi::temp_file_mapping>(cache_dir / filename, num_samples * bytes_per_sample);
|
||||
decoder = std::thread([&] {
|
||||
int64_t block = 65536;
|
||||
for (int64_t i = 0; i < num_samples; i += block) {
|
||||
if (cancelled) break;
|
||||
block = std::min(block, num_samples - i);
|
||||
source->GetAudio(file->write(i * bytes_per_sample, block * bytes_per_sample), i, block);
|
||||
source->GetAudio(file.write(i * bytes_per_sample, block * bytes_per_sample), i, block);
|
||||
decoded_samples += block;
|
||||
}
|
||||
});
|
||||
|
@ -86,6 +82,8 @@ public:
|
|||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioProvider> CreateHDAudioProvider(std::unique_ptr<AudioProvider> src) {
|
||||
return agi::make_unique<HDAudioProvider>(std::move(src));
|
||||
namespace agi {
|
||||
std::unique_ptr<AudioProvider> CreateHDAudioProvider(std::unique_ptr<AudioProvider> src, agi::fs::path const& dir) {
|
||||
return make_unique<HDAudioProvider>(std::move(src), dir);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2012, Thomas Goyne <plorkyeran@aegisub.org>
|
||||
// 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
|
||||
|
@ -11,20 +11,17 @@
|
|||
// 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_lock.cpp
|
||||
/// @brief An audio provider adapter for un-threadsafe audio providers
|
||||
/// @ingroup audio_input
|
||||
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
#include "libaegisub/audio/provider.h"
|
||||
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace {
|
||||
class LockAudioProvider final : public AudioProviderWrapper {
|
||||
class LockAudioProvider final : public agi::AudioProviderWrapper {
|
||||
mutable std::mutex mutex;
|
||||
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count) const override {
|
||||
|
@ -40,6 +37,8 @@ public:
|
|||
};
|
||||
}
|
||||
|
||||
namespace agi {
|
||||
std::unique_ptr<AudioProvider> CreateLockAudioProvider(std::unique_ptr<AudioProvider> src) {
|
||||
return agi::make_unique<LockAudioProvider>(std::move(src));
|
||||
}
|
||||
}
|
239
libaegisub/audio/provider_pcm.cpp
Normal file
239
libaegisub/audio/provider_pcm.cpp
Normal file
|
@ -0,0 +1,239 @@
|
|||
// 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 "libaegisub/audio/provider.h"
|
||||
|
||||
#include "libaegisub/file_mapping.h"
|
||||
#include "libaegisub/fs.h"
|
||||
#include "libaegisub/make_unique.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
using namespace agi;
|
||||
|
||||
struct IndexPoint {
|
||||
uint64_t start_byte;
|
||||
uint64_t num_samples;
|
||||
};
|
||||
|
||||
struct file_ended {};
|
||||
|
||||
class PCMAudioProvider : public AudioProvider {
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count) const override {
|
||||
auto write_buf = static_cast<char *>(buf);
|
||||
auto bps = bytes_per_sample * channels;
|
||||
uint64_t pos = 0;
|
||||
|
||||
for (auto ip : index_points) {
|
||||
if (count == 0) break;
|
||||
if (pos + ip.num_samples <= (uint64_t)start) {
|
||||
pos += ip.num_samples;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto read_offset = start - pos;
|
||||
auto read_count = std::min<size_t>(count, ip.num_samples - read_offset);
|
||||
auto bytes = read_count * bps;
|
||||
memcpy(write_buf, file.read(ip.start_byte + read_offset * bps, bytes), bytes);
|
||||
|
||||
write_buf += bytes;
|
||||
count -= read_count;
|
||||
start += read_count;
|
||||
pos += ip.num_samples;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
mutable read_file_mapping file;
|
||||
uint64_t file_pos = 0;
|
||||
|
||||
PCMAudioProvider(fs::path const& filename) : file(filename) { }
|
||||
|
||||
template<typename T, typename UInt>
|
||||
const T *Read(UInt *data_left) {
|
||||
if (*data_left < sizeof(T)) throw file_ended();
|
||||
if (file.size() - file_pos < sizeof(T)) throw file_ended();
|
||||
|
||||
auto data = file.read(file_pos, sizeof(T));
|
||||
file_pos += sizeof(T);
|
||||
*data_left -= sizeof(T);
|
||||
return reinterpret_cast<const T *>(data);
|
||||
}
|
||||
|
||||
std::vector<IndexPoint> index_points;
|
||||
};
|
||||
|
||||
struct FourCC {
|
||||
std::array<char, 4> data;
|
||||
bool operator!=(const char *cmp) const {
|
||||
return data[0] != cmp[0] || data[1] != cmp[1]
|
||||
|| data[2] != cmp[2] || data[3] != cmp[3];
|
||||
}
|
||||
bool operator==(const char *cmp) const { return !(*this != cmp); }
|
||||
};
|
||||
|
||||
// Overview of RIFF WAV: <http://www.sonicspot.com/guide/wavefiles.html>
|
||||
struct RiffWav {
|
||||
using DataSize = uint32_t;
|
||||
using ChunkId = FourCC;
|
||||
|
||||
static const char *riff_id() { return "RIFF"; }
|
||||
static const char *wave_id() { return "WAVE"; }
|
||||
static const char *fmt_id() { return "fmt "; }
|
||||
static const char *data_id() { return "data "; }
|
||||
|
||||
static const int alignment = 1;
|
||||
|
||||
static uint32_t data_size(uint32_t size) { return size; }
|
||||
static uint32_t chunk_size(uint32_t size) { return size; }
|
||||
};
|
||||
|
||||
typedef std::array<uint8_t, 16> GUID;
|
||||
|
||||
static const GUID w64GuidRIFF = {{
|
||||
// {66666972-912E-11CF-A5D6-28DB04C10000}
|
||||
0x72, 0x69, 0x66, 0x66, 0x2E, 0x91, 0xCF, 0x11, 0xA5, 0xD6, 0x28, 0xDB, 0x04, 0xC1, 0x00, 0x00
|
||||
}};
|
||||
|
||||
static const GUID w64GuidWAVE = {{
|
||||
// {65766177-ACF3-11D3-8CD1-00C04F8EDB8A}
|
||||
0x77, 0x61, 0x76, 0x65, 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A
|
||||
}};
|
||||
|
||||
static const GUID w64Guidfmt = {{
|
||||
// {20746D66-ACF3-11D3-8CD1-00C04F8EDB8A}
|
||||
0x66, 0x6D, 0x74, 0x20, 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A
|
||||
}};
|
||||
|
||||
static const GUID w64Guiddata = {{
|
||||
// {61746164-ACF3-11D3-8CD1-00C04F8EDB8A}
|
||||
0x64, 0x61, 0x74, 0x61, 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A
|
||||
}};
|
||||
|
||||
// http://www.vcs.de/fileadmin/user_upload/MBS/PDF/Whitepaper/Informations_about_Sony_Wave64.pdf
|
||||
struct Wave64 {
|
||||
using DataSize = uint64_t;
|
||||
using ChunkId = GUID;
|
||||
|
||||
static GUID riff_id() { return w64GuidRIFF; }
|
||||
static GUID wave_id() { return w64GuidWAVE; }
|
||||
static GUID fmt_id() { return w64Guidfmt; }
|
||||
static GUID data_id() { return w64Guiddata; }
|
||||
|
||||
static const uint64_t alignment = 7ULL;
|
||||
|
||||
// Wave 64 includes the size of the header in the chunk sizes
|
||||
static uint64_t data_size(uint64_t size) { return size - 16; }
|
||||
static uint64_t chunk_size(uint64_t size) { return size - 24; }
|
||||
};
|
||||
|
||||
template<typename Impl>
|
||||
class WavPCMAudioProvider : public PCMAudioProvider {
|
||||
public:
|
||||
WavPCMAudioProvider(fs::path const& filename)
|
||||
: PCMAudioProvider(filename)
|
||||
{
|
||||
using DataSize = typename Impl::DataSize;
|
||||
using ChunkId = typename Impl::ChunkId;
|
||||
|
||||
try {
|
||||
auto data_left = std::numeric_limits<DataSize>::max();
|
||||
if (*Read<ChunkId>(&data_left) != Impl::riff_id())
|
||||
throw AudioDataNotFound("File is not a RIFF file");
|
||||
|
||||
data_left = Impl::data_size(*Read<DataSize>(&data_left));
|
||||
|
||||
if (*Read<ChunkId>(&data_left) != Impl::wave_id())
|
||||
throw AudioDataNotFound("File is not a RIFF WAV file");
|
||||
|
||||
while (data_left) {
|
||||
auto chunk_fcc = *Read<ChunkId>(&data_left);
|
||||
auto chunk_size = Impl::chunk_size(*Read<DataSize>(&data_left));
|
||||
|
||||
data_left -= chunk_size;
|
||||
|
||||
if (chunk_fcc == Impl::fmt_id()) {
|
||||
if (channels || sample_rate || bytes_per_sample)
|
||||
throw AudioProviderError("Multiple 'fmt ' chunks not supported");
|
||||
|
||||
auto compression = *Read<uint16_t>(&chunk_size);
|
||||
if (compression != 1)
|
||||
throw AudioProviderError("File is not uncompressed PCM");
|
||||
|
||||
channels = *Read<uint16_t>(&chunk_size);
|
||||
sample_rate = *Read<uint32_t>(&chunk_size);
|
||||
Read<uint32_t>(&chunk_size); // Average bytes per sample; meaningless
|
||||
Read<uint16_t>(&chunk_size); // Block align
|
||||
bytes_per_sample = (*Read<uint16_t>(&chunk_size) + 7) / 8;
|
||||
}
|
||||
else if (chunk_fcc == Impl::data_id()) {
|
||||
if (!channels || !sample_rate || !bytes_per_sample)
|
||||
throw AudioProviderError("Found 'data' chunk without format being set.");
|
||||
index_points.emplace_back(IndexPoint{file_pos, chunk_size / bytes_per_sample / channels});
|
||||
num_samples += chunk_size / bytes_per_sample / channels;
|
||||
}
|
||||
// There's a bunch of other chunk types. They're all dumb.
|
||||
|
||||
// blocks are aligned and the padding bytes are not included in
|
||||
// the size of the chunk
|
||||
file_pos += (chunk_size + Impl::alignment) & ~Impl::alignment;
|
||||
}
|
||||
|
||||
}
|
||||
catch (file_ended) {
|
||||
if (!channels || !sample_rate || !bytes_per_sample)
|
||||
throw AudioDataNotFound("File ended before reaching format chunk");
|
||||
// Truncated files are fine otherwise
|
||||
}
|
||||
decoded_samples = num_samples;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace agi {
|
||||
std::unique_ptr<AudioProvider> CreatePCMAudioProvider(fs::path const& filename, BackgroundRunner *) {
|
||||
bool wrong_file_type = true;
|
||||
std::string msg;
|
||||
|
||||
try {
|
||||
return make_unique<WavPCMAudioProvider<RiffWav>>(filename);
|
||||
}
|
||||
catch (AudioDataNotFound const& err) {
|
||||
msg = "RIFF PCM WAV audio provider: " + err.GetMessage();
|
||||
}
|
||||
catch (AudioProviderError const& err) {
|
||||
wrong_file_type = false;
|
||||
msg = "RIFF PCM WAV audio provider: " + err.GetMessage();
|
||||
}
|
||||
|
||||
try {
|
||||
return make_unique<WavPCMAudioProvider<Wave64>>(filename);
|
||||
}
|
||||
catch (AudioDataNotFound const& err) {
|
||||
msg += "\nWave64 audio provider: " + err.GetMessage();
|
||||
}
|
||||
catch (AudioProviderError const& err) {
|
||||
wrong_file_type = false;
|
||||
msg += "\nWave64 audio provider: " + err.GetMessage();
|
||||
}
|
||||
|
||||
if (wrong_file_type)
|
||||
throw AudioDataNotFound(msg);
|
||||
else
|
||||
throw AudioProviderError(msg);
|
||||
}
|
||||
}
|
|
@ -1,48 +1,29 @@
|
|||
// Copyright (c) 2005-2006, Rodrigo Braz Monteiro, Fredrik Mellbin
|
||||
// All rights reserved.
|
||||
// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
// 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.
|
||||
//
|
||||
// * 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.
|
||||
// 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_ram.cpp
|
||||
/// @brief Caching audio provider using heap memory for backing
|
||||
/// @ingroup audio_input
|
||||
///
|
||||
#include "libaegisub/audio/provider.h"
|
||||
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include "audio_controller.h"
|
||||
|
||||
#include <libaegisub/make_unique.h>
|
||||
#include "libaegisub/make_unique.h"
|
||||
|
||||
#include <array>
|
||||
#include <boost/container/stable_vector.hpp>
|
||||
#include <thread>
|
||||
|
||||
namespace {
|
||||
using namespace agi;
|
||||
|
||||
#define CacheBits 22
|
||||
#define CacheBlockSize (1 << CacheBits)
|
||||
|
@ -68,7 +49,7 @@ public:
|
|||
blockcache.resize((source->GetNumSamples() * source->GetBytesPerSample() + CacheBlockSize - 1) >> CacheBits);
|
||||
}
|
||||
catch (std::bad_alloc const&) {
|
||||
throw agi::AudioCacheOpenError("Couldn't open audio, not enough ram available.");
|
||||
throw AudioProviderError("Not enough memory available to cache in RAM");
|
||||
}
|
||||
|
||||
decoder = std::thread([&] {
|
||||
|
@ -108,6 +89,8 @@ void RAMAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const
|
|||
}
|
||||
}
|
||||
|
||||
namespace agi {
|
||||
std::unique_ptr<AudioProvider> CreateRAMAudioProvider(std::unique_ptr<AudioProvider> src) {
|
||||
return agi::make_unique<RAMAudioProvider>(std::move(src));
|
||||
return make_unique<RAMAudioProvider>(std::move(src));
|
||||
}
|
||||
}
|
95
libaegisub/include/libaegisub/audio/provider.h
Normal file
95
libaegisub/include/libaegisub/audio/provider.h
Normal file
|
@ -0,0 +1,95 @@
|
|||
// 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/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libaegisub/exception.h>
|
||||
#include <libaegisub/fs_fwd.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
namespace agi {
|
||||
class AudioProvider {
|
||||
protected:
|
||||
int channels = 0;
|
||||
/// Total number of samples per channel
|
||||
int64_t num_samples = 0;
|
||||
/// Samples per channel which have been decoded and can be fetched with FillBuffer
|
||||
/// Only applicable for the cache providers
|
||||
std::atomic<int64_t> decoded_samples{0};
|
||||
int sample_rate = 0;
|
||||
int bytes_per_sample = 0;
|
||||
bool float_samples = false;
|
||||
|
||||
virtual void FillBuffer(void *buf, int64_t start, int64_t count) const = 0;
|
||||
|
||||
void ZeroFill(void *buf, int64_t count) const;
|
||||
|
||||
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;
|
||||
|
||||
int64_t GetNumSamples() const { return num_samples; }
|
||||
int64_t GetDecodedSamples() const { return decoded_samples; }
|
||||
int GetSampleRate() const { return sample_rate; }
|
||||
int GetBytesPerSample() const { return bytes_per_sample; }
|
||||
int GetChannels() const { return channels; }
|
||||
bool AreSamplesFloat() const { return float_samples; }
|
||||
|
||||
/// Does this provider benefit from external caching?
|
||||
virtual bool NeedsCache() const { return false; }
|
||||
};
|
||||
|
||||
/// Helper base class for an audio provider which wraps another provider
|
||||
class AudioProviderWrapper : public AudioProvider {
|
||||
protected:
|
||||
std::unique_ptr<AudioProvider> source;
|
||||
public:
|
||||
AudioProviderWrapper(std::unique_ptr<AudioProvider> src)
|
||||
: source(std::move(src))
|
||||
{
|
||||
channels = source->GetChannels();
|
||||
num_samples = source->GetNumSamples();
|
||||
decoded_samples = source->GetDecodedSamples();
|
||||
sample_rate = source->GetSampleRate();
|
||||
bytes_per_sample = source->GetBytesPerSample();
|
||||
float_samples = source->AreSamplesFloat();
|
||||
}
|
||||
};
|
||||
|
||||
DEFINE_EXCEPTION(AudioProviderError, Exception);
|
||||
|
||||
/// Error of some sort occurred while decoding a frame
|
||||
DEFINE_EXCEPTION(AudioDecodeError, AudioProviderError);
|
||||
|
||||
/// This provider could not find any audio data in the file
|
||||
DEFINE_EXCEPTION(AudioDataNotFound, AudioProviderError);
|
||||
|
||||
class BackgroundRunner;
|
||||
|
||||
std::unique_ptr<AudioProvider> CreateDummyAudioProvider(fs::path const& filename, BackgroundRunner *);
|
||||
std::unique_ptr<AudioProvider> CreatePCMAudioProvider(fs::path const& filename, BackgroundRunner *);
|
||||
|
||||
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, fs::path const& dir);
|
||||
std::unique_ptr<AudioProvider> CreateRAMAudioProvider(std::unique_ptr<AudioProvider> source_provider);
|
||||
|
||||
void SaveAudioClip(AudioProvider *provider, fs::path const& path, int start_time, int end_time);
|
||||
}
|
10
src/Makefile
10
src/Makefile
|
@ -36,13 +36,7 @@ src_OBJ := \
|
|||
$(d)audio_karaoke.o \
|
||||
$(d)audio_marker.o \
|
||||
$(d)audio_player.o \
|
||||
$(d)audio_provider.o \
|
||||
$(d)audio_provider_convert.o \
|
||||
$(d)audio_provider_dummy.o \
|
||||
$(d)audio_provider_hd.o \
|
||||
$(d)audio_provider_lock.o \
|
||||
$(d)audio_provider_pcm.o \
|
||||
$(d)audio_provider_ram.o \
|
||||
$(d)audio_provider_factory.o \
|
||||
$(d)audio_renderer.o \
|
||||
$(d)audio_renderer_spectrum.o \
|
||||
$(d)audio_renderer_waveform.o \
|
||||
|
@ -190,7 +184,7 @@ endif
|
|||
#####################
|
||||
$(d)MatroskaParser.o_FLAGS := -Wno-sometimes-uninitialized
|
||||
$(d)audio_player.o_FLAGS := $(CFLAGS_ALSA) $(CFLAGS_PORTAUDIO) $(CFLAGS_LIBPULSE) $(CFLAGS_OPENAL)
|
||||
$(d)audio_provider.o_FLAGS := $(CFLAGS_FFMS2)
|
||||
$(d)audio_provider_factory.o_FLAGS := $(CFLAGS_FFMS2)
|
||||
$(d)auto4_base.o_FLAGS := $(CFLAGS_FREETYPE)
|
||||
$(d)charset_detect.o_FLAGS := -D_X86_
|
||||
$(d)font_file_lister_fontconfig.o_FLAGS := $(CFLAGS_FONTCONFIG)
|
||||
|
|
|
@ -31,11 +31,12 @@
|
|||
|
||||
#include "audio_timing.h"
|
||||
#include "include/aegisub/audio_player.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
#include "include/aegisub/context.h"
|
||||
#include "options.h"
|
||||
#include "project.h"
|
||||
|
||||
#include <libaegisub/audio/provider.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
AudioController::AudioController(agi::Context *context)
|
||||
|
@ -102,12 +103,13 @@ void AudioController::OnAudioPlayerChanged()
|
|||
}
|
||||
catch (...)
|
||||
{
|
||||
/// @todo This really shouldn't be just swallowing all audio player open errors
|
||||
context->project->CloseAudio();
|
||||
}
|
||||
AnnounceAudioPlayerOpened();
|
||||
}
|
||||
|
||||
void AudioController::OnAudioProvider(AudioProvider *new_provider)
|
||||
void AudioController::OnAudioProvider(agi::AudioProvider *new_provider)
|
||||
{
|
||||
provider = new_provider;
|
||||
Stop();
|
||||
|
|
|
@ -36,10 +36,10 @@
|
|||
#include <wx/timer.h>
|
||||
|
||||
class AudioPlayer;
|
||||
class AudioProvider;
|
||||
class AudioTimingController;
|
||||
namespace agi { struct Context; }
|
||||
class TimeRange;
|
||||
namespace agi { class AudioProvider; }
|
||||
namespace agi { struct Context; }
|
||||
|
||||
/// @class AudioController
|
||||
/// @brief Manage playback of an open audio stream
|
||||
|
@ -84,10 +84,10 @@ class AudioController final : public wxEvtHandler {
|
|||
wxTimer playback_timer;
|
||||
|
||||
/// The audio provider
|
||||
AudioProvider *provider = nullptr;
|
||||
agi::AudioProvider *provider = nullptr;
|
||||
agi::signal::Connection provider_connection;
|
||||
|
||||
void OnAudioProvider(AudioProvider *new_provider);
|
||||
void OnAudioProvider(agi::AudioProvider *new_provider);
|
||||
|
||||
/// Event handler for the playback timer
|
||||
void OnPlaybackTimer(wxTimerEvent &event);
|
||||
|
@ -189,29 +189,3 @@ public:
|
|||
DEFINE_SIGNAL_ADDERS(AnnounceTimingControllerChanged, AddTimingControllerListener)
|
||||
DEFINE_SIGNAL_ADDERS(AnnounceAudioPlayerOpened, AddAudioPlayerOpenListener)
|
||||
};
|
||||
|
||||
namespace agi {
|
||||
/// Base class for all audio-related errors
|
||||
DEFINE_EXCEPTION(AudioError, Exception);
|
||||
|
||||
/// Opening the audio failed for any reason
|
||||
DEFINE_EXCEPTION(AudioOpenError, AudioError);
|
||||
|
||||
/// There are no audio providers available to open audio files
|
||||
DEFINE_EXCEPTION(NoAudioProvidersError, AudioOpenError);
|
||||
|
||||
/// The file exists, but no providers could find any audio tracks in it
|
||||
DEFINE_EXCEPTION(AudioDataNotFoundError, AudioOpenError);
|
||||
|
||||
/// There are audio tracks, but no provider could actually read them
|
||||
DEFINE_EXCEPTION(AudioProviderOpenError, AudioOpenError);
|
||||
|
||||
/// The audio cache failed to initialize
|
||||
DEFINE_EXCEPTION(AudioCacheOpenError, AudioOpenError);
|
||||
|
||||
/// There are no audio players available
|
||||
DEFINE_EXCEPTION(NoAudioPlayersError, AudioOpenError);
|
||||
|
||||
/// The audio player failed to initialize
|
||||
DEFINE_EXCEPTION(AudioPlayerOpenError, AudioOpenError);
|
||||
}
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
#include "audio_timing.h"
|
||||
#include "compat.h"
|
||||
#include "format.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
#include "include/aegisub/context.h"
|
||||
#include "include/aegisub/hotkey.h"
|
||||
#include "options.h"
|
||||
|
@ -46,6 +45,7 @@
|
|||
#include "video_controller.h"
|
||||
|
||||
#include <libaegisub/ass/time.h>
|
||||
#include <libaegisub/audio/provider.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
@ -1171,7 +1171,7 @@ int AudioDisplay::GetDuration() const
|
|||
return (provider->GetNumSamples() * 1000 + provider->GetSampleRate() - 1) / provider->GetSampleRate();
|
||||
}
|
||||
|
||||
void AudioDisplay::OnAudioOpen(AudioProvider *provider)
|
||||
void AudioDisplay::OnAudioOpen(agi::AudioProvider *provider)
|
||||
{
|
||||
this->provider = provider;
|
||||
|
||||
|
|
|
@ -40,12 +40,12 @@
|
|||
#include <wx/timer.h>
|
||||
#include <wx/window.h>
|
||||
|
||||
namespace agi { class AudioProvider; }
|
||||
namespace agi { struct Context; }
|
||||
|
||||
class AudioController;
|
||||
class AudioRenderer;
|
||||
class AudioRendererBitmapProvider;
|
||||
class AudioProvider;
|
||||
class TimeRange;
|
||||
|
||||
// Helper classes used in implementation of the audio display
|
||||
|
@ -107,7 +107,7 @@ class AudioDisplay: public wxWindow {
|
|||
/// The controller managing us
|
||||
AudioController *controller = nullptr;
|
||||
|
||||
AudioProvider *provider = nullptr;
|
||||
agi::AudioProvider *provider = nullptr;
|
||||
|
||||
/// Scrollbar helper object
|
||||
std::unique_ptr<AudioDisplayScrollbar> scrollbar;
|
||||
|
@ -230,7 +230,7 @@ class AudioDisplay: public wxWindow {
|
|||
|
||||
int GetDuration() const;
|
||||
|
||||
void OnAudioOpen(AudioProvider *provider);
|
||||
void OnAudioOpen(agi::AudioProvider *provider);
|
||||
void OnPlaybackPosition(int ms_position);
|
||||
void OnSelectionChanged();
|
||||
void OnStyleRangesChanged();
|
||||
|
|
|
@ -120,7 +120,7 @@ void AudioKaraoke::OnFileChanged(int type, std::set<const AssDialogue *> const&
|
|||
}
|
||||
}
|
||||
|
||||
void AudioKaraoke::OnAudioOpened(AudioProvider *provider) {
|
||||
void AudioKaraoke::OnAudioOpened(agi::AudioProvider *provider) {
|
||||
if (provider)
|
||||
SetEnabled(enabled);
|
||||
else
|
||||
|
|
|
@ -26,8 +26,8 @@
|
|||
|
||||
class AssDialogue;
|
||||
class AssKaraoke;
|
||||
class AudioProvider;
|
||||
class wxButton;
|
||||
namespace agi { class AudioProvider; }
|
||||
namespace agi { struct Context; }
|
||||
|
||||
/// @class AudioKaraoke
|
||||
|
@ -141,7 +141,7 @@ class AudioKaraoke final : public wxWindow {
|
|||
void OnMouse(wxMouseEvent &event);
|
||||
void OnPaint(wxPaintEvent &event);
|
||||
void OnSize(wxSizeEvent &event);
|
||||
void OnAudioOpened(AudioProvider *provider);
|
||||
void OnAudioOpened(agi::AudioProvider *provider);
|
||||
void OnScrollTimer(wxTimerEvent &event);
|
||||
|
||||
public:
|
||||
|
|
|
@ -40,23 +40,18 @@
|
|||
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
|
||||
AudioPlayer::AudioPlayer(AudioProvider *provider)
|
||||
: provider(provider)
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioPlayer> CreateAlsaPlayer(AudioProvider *providers, wxWindow *window);
|
||||
std::unique_ptr<AudioPlayer> CreateDirectSoundPlayer(AudioProvider *providers, wxWindow *window);
|
||||
std::unique_ptr<AudioPlayer> CreateDirectSound2Player(AudioProvider *providers, wxWindow *window);
|
||||
std::unique_ptr<AudioPlayer> CreateOpenALPlayer(AudioProvider *providers, wxWindow *window);
|
||||
std::unique_ptr<AudioPlayer> CreatePortAudioPlayer(AudioProvider *providers, wxWindow *window);
|
||||
std::unique_ptr<AudioPlayer> CreatePulseAudioPlayer(AudioProvider *providers, wxWindow *window);
|
||||
std::unique_ptr<AudioPlayer> CreateOSSPlayer(AudioProvider *providers, wxWindow *window);
|
||||
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> 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);
|
||||
std::unique_ptr<AudioPlayer> CreateOSSPlayer(agi::AudioProvider *providers, wxWindow *window);
|
||||
|
||||
namespace {
|
||||
struct factory {
|
||||
const char *name;
|
||||
std::unique_ptr<AudioPlayer> (*create)(AudioProvider *, wxWindow *window);
|
||||
std::unique_ptr<AudioPlayer> (*create)(agi::AudioProvider *, wxWindow *window);
|
||||
bool hidden;
|
||||
};
|
||||
|
||||
|
@ -87,9 +82,9 @@ std::vector<std::string> AudioPlayerFactory::GetClasses() {
|
|||
return ::GetClasses(boost::make_iterator_range(std::begin(factories), std::end(factories)));
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioPlayer> AudioPlayerFactory::GetAudioPlayer(AudioProvider *provider, wxWindow *window) {
|
||||
std::unique_ptr<AudioPlayer> AudioPlayerFactory::GetAudioPlayer(agi::AudioProvider *provider, wxWindow *window) {
|
||||
if (std::begin(factories) == std::end(factories))
|
||||
throw agi::NoAudioPlayersError("No audio players are available.");
|
||||
throw AudioPlayerOpenError("No audio players are available.");
|
||||
|
||||
auto preferred = OPT_GET("Audio/Player")->GetString();
|
||||
auto sorted = GetSorted(boost::make_iterator_range(std::begin(factories), std::end(factories)), preferred);
|
||||
|
@ -99,9 +94,9 @@ std::unique_ptr<AudioPlayer> AudioPlayerFactory::GetAudioPlayer(AudioProvider *p
|
|||
try {
|
||||
return factory->create(provider, window);
|
||||
}
|
||||
catch (agi::AudioPlayerOpenError const& err) {
|
||||
catch (AudioPlayerOpenError const& err) {
|
||||
error += std::string(factory->name) + " factory: " + err.GetMessage() + "\n";
|
||||
}
|
||||
}
|
||||
throw agi::AudioPlayerOpenError(error);
|
||||
throw AudioPlayerOpenError(error);
|
||||
}
|
||||
|
|
|
@ -36,11 +36,11 @@
|
|||
#include "include/aegisub/audio_player.h"
|
||||
|
||||
#include "audio_controller.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
#include "compat.h"
|
||||
#include "frame_main.h"
|
||||
#include "options.h"
|
||||
|
||||
#include <libaegisub/audio/provider.h>
|
||||
#include <libaegisub/log.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
|
@ -102,7 +102,7 @@ class AlsaPlayer final : public AudioPlayer {
|
|||
}
|
||||
|
||||
public:
|
||||
AlsaPlayer(AudioProvider *provider);
|
||||
AlsaPlayer(agi::AudioProvider *provider);
|
||||
~AlsaPlayer();
|
||||
|
||||
void Play(int64_t start, int64_t count) override;
|
||||
|
@ -289,13 +289,13 @@ do_setup:
|
|||
}
|
||||
}
|
||||
|
||||
AlsaPlayer::AlsaPlayer(AudioProvider *provider) try
|
||||
AlsaPlayer::AlsaPlayer(agi::AudioProvider *provider) try
|
||||
: AudioPlayer(provider)
|
||||
, thread(&AlsaPlayer::PlaybackThread, this)
|
||||
{
|
||||
}
|
||||
catch (std::system_error const&) {
|
||||
throw agi::AudioPlayerOpenError("AlsaPlayer: Creating the playback thread failed");
|
||||
throw AudioPlayerOpenError("AlsaPlayer: Creating the playback thread failed");
|
||||
}
|
||||
|
||||
AlsaPlayer::~AlsaPlayer()
|
||||
|
@ -347,7 +347,7 @@ int64_t AlsaPlayer::GetCurrentPosition()
|
|||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioPlayer> CreateAlsaPlayer(AudioProvider *provider, wxWindow *)
|
||||
std::unique_ptr<AudioPlayer> CreateAlsaPlayer(agi::AudioProvider *provider, wxWindow *)
|
||||
{
|
||||
return agi::make_unique<AlsaPlayer>(provider);
|
||||
}
|
||||
|
|
|
@ -36,10 +36,10 @@
|
|||
#include "include/aegisub/audio_player.h"
|
||||
|
||||
#include "audio_controller.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
#include "frame_main.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <libaegisub/audio/provider.h>
|
||||
#include <libaegisub/log.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
|
@ -81,7 +81,7 @@ class DirectSoundPlayer final : public AudioPlayer {
|
|||
DirectSoundPlayerThread *thread = nullptr;
|
||||
|
||||
public:
|
||||
DirectSoundPlayer(AudioProvider *provider, wxWindow *parent);
|
||||
DirectSoundPlayer(agi::AudioProvider *provider, wxWindow *parent);
|
||||
~DirectSoundPlayer();
|
||||
|
||||
void Play(int64_t start,int64_t count);
|
||||
|
@ -96,13 +96,13 @@ public:
|
|||
void SetVolume(double vol) { volume = vol; }
|
||||
};
|
||||
|
||||
DirectSoundPlayer::DirectSoundPlayer(AudioProvider *provider, wxWindow *parent)
|
||||
DirectSoundPlayer::DirectSoundPlayer(agi::AudioProvider *provider, wxWindow *parent)
|
||||
: AudioPlayer(provider)
|
||||
{
|
||||
// Initialize the DirectSound object
|
||||
HRESULT res;
|
||||
res = DirectSoundCreate8(&DSDEVID_DefaultPlayback,&directSound,nullptr); // TODO: support selecting audio device
|
||||
if (FAILED(res)) throw agi::AudioPlayerOpenError("Failed initializing DirectSound");
|
||||
if (FAILED(res)) throw AudioPlayerOpenError("Failed initializing DirectSound");
|
||||
|
||||
// Set DirectSound parameters
|
||||
directSound->SetCooperativeLevel((HWND)parent->GetHandle(),DSSCL_PRIORITY);
|
||||
|
@ -133,11 +133,11 @@ DirectSoundPlayer::DirectSoundPlayer(AudioProvider *provider, wxWindow *parent)
|
|||
// Create the buffer
|
||||
IDirectSoundBuffer *buf;
|
||||
res = directSound->CreateSoundBuffer(&desc,&buf,nullptr);
|
||||
if (res != DS_OK) throw agi::AudioPlayerOpenError("Failed creating DirectSound buffer");
|
||||
if (res != DS_OK) throw AudioPlayerOpenError("Failed creating DirectSound buffer");
|
||||
|
||||
// Copy interface to buffer
|
||||
res = buf->QueryInterface(IID_IDirectSoundBuffer8,(LPVOID*) &buffer);
|
||||
if (res != S_OK) throw agi::AudioPlayerOpenError("Failed casting interface to IDirectSoundBuffer8");
|
||||
if (res != S_OK) throw AudioPlayerOpenError("Failed casting interface to IDirectSoundBuffer8");
|
||||
|
||||
// Set data
|
||||
offset = 0;
|
||||
|
@ -367,7 +367,7 @@ void DirectSoundPlayerThread::Stop() {
|
|||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioPlayer> CreateDirectSoundPlayer(AudioProvider *provider, wxWindow *parent) {
|
||||
std::unique_ptr<AudioPlayer> CreateDirectSoundPlayer(agi::AudioProvider *provider, wxWindow *parent) {
|
||||
return agi::make_unique<DirectSoundPlayer>(provider, parent);
|
||||
}
|
||||
|
||||
|
|
|
@ -36,11 +36,11 @@
|
|||
#include "include/aegisub/audio_player.h"
|
||||
|
||||
#include "audio_controller.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
#include "frame_main.h"
|
||||
#include "options.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <libaegisub/audio/provider.h>
|
||||
#include <libaegisub/scoped_ptr.h>
|
||||
#include <libaegisub/log.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
@ -74,7 +74,7 @@ class DirectSoundPlayer2 final : public AudioPlayer {
|
|||
|
||||
public:
|
||||
/// @brief Constructor
|
||||
DirectSoundPlayer2(AudioProvider *provider, wxWindow *parent);
|
||||
DirectSoundPlayer2(agi::AudioProvider *provider, wxWindow *parent);
|
||||
/// @brief Destructor
|
||||
~DirectSoundPlayer2();
|
||||
|
||||
|
@ -236,14 +236,14 @@ class DirectSoundPlayer2Thread {
|
|||
DWORD last_playback_restart;
|
||||
|
||||
/// Audio provider to take sample data from
|
||||
AudioProvider *provider;
|
||||
agi::AudioProvider *provider;
|
||||
|
||||
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
|
||||
DirectSoundPlayer2Thread(AudioProvider *provider, int WantedLatency, int BufferLength, wxWindow *parent);
|
||||
DirectSoundPlayer2Thread(agi::AudioProvider *provider, int WantedLatency, int BufferLength, wxWindow *parent);
|
||||
/// @brief Destructor, waits for thread to have died
|
||||
~DirectSoundPlayer2Thread();
|
||||
|
||||
|
@ -660,7 +660,7 @@ void DirectSoundPlayer2Thread::CheckError()
|
|||
}
|
||||
}
|
||||
|
||||
DirectSoundPlayer2Thread::DirectSoundPlayer2Thread(AudioProvider *provider, int WantedLatency, int BufferLength, wxWindow *parent)
|
||||
DirectSoundPlayer2Thread::DirectSoundPlayer2Thread(agi::AudioProvider *provider, int WantedLatency, int BufferLength, wxWindow *parent)
|
||||
: parent((HWND)parent->GetHandle())
|
||||
, event_start_playback (CreateEvent(0, FALSE, FALSE, 0))
|
||||
, event_stop_playback (CreateEvent(0, FALSE, FALSE, 0))
|
||||
|
@ -677,7 +677,7 @@ DirectSoundPlayer2Thread::DirectSoundPlayer2Thread(AudioProvider *provider, int
|
|||
thread_handle = (HANDLE)_beginthreadex(0, 0, ThreadProc, this, 0, 0);
|
||||
|
||||
if (!thread_handle)
|
||||
throw agi::AudioPlayerOpenError("Failed creating playback thread in DirectSoundPlayer2. This is bad.");
|
||||
throw AudioPlayerOpenError("Failed creating playback thread in DirectSoundPlayer2. This is bad.");
|
||||
|
||||
HANDLE running_or_error[] = { thread_running, error_happened };
|
||||
switch (WaitForMultipleObjects(2, running_or_error, FALSE, INFINITE))
|
||||
|
@ -688,10 +688,10 @@ DirectSoundPlayer2Thread::DirectSoundPlayer2Thread(AudioProvider *provider, int
|
|||
|
||||
case WAIT_OBJECT_0 + 1:
|
||||
// error happened, we fail
|
||||
throw agi::AudioPlayerOpenError(error_message);
|
||||
throw AudioPlayerOpenError(error_message);
|
||||
|
||||
default:
|
||||
throw agi::AudioPlayerOpenError("Failed wait for thread start or thread error in DirectSoundPlayer2. This is bad.");
|
||||
throw AudioPlayerOpenError("Failed wait for thread start or thread error in DirectSoundPlayer2. This is bad.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -800,7 +800,7 @@ bool DirectSoundPlayer2Thread::IsDead()
|
|||
}
|
||||
}
|
||||
|
||||
DirectSoundPlayer2::DirectSoundPlayer2(AudioProvider *provider, wxWindow *parent)
|
||||
DirectSoundPlayer2::DirectSoundPlayer2(agi::AudioProvider *provider, wxWindow *parent)
|
||||
: AudioPlayer(provider)
|
||||
{
|
||||
// The buffer will hold BufferLength times WantedLatency milliseconds of audio
|
||||
|
@ -820,7 +820,7 @@ DirectSoundPlayer2::DirectSoundPlayer2(AudioProvider *provider, wxWindow *parent
|
|||
catch (const char *msg)
|
||||
{
|
||||
LOG_E("audio/player/dsound") << msg;
|
||||
throw agi::AudioPlayerOpenError(msg);
|
||||
throw AudioPlayerOpenError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -929,7 +929,7 @@ void DirectSoundPlayer2::SetVolume(double vol)
|
|||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioPlayer> CreateDirectSound2Player(AudioProvider *provider, wxWindow *parent) {
|
||||
std::unique_ptr<AudioPlayer> CreateDirectSound2Player(agi::AudioProvider *provider, wxWindow *parent) {
|
||||
return agi::make_unique<DirectSoundPlayer2>(provider, parent);
|
||||
}
|
||||
|
||||
|
|
|
@ -36,9 +36,9 @@
|
|||
#include "include/aegisub/audio_player.h"
|
||||
|
||||
#include "audio_controller.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <libaegisub/audio/provider.h>
|
||||
#include <libaegisub/log.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
|
@ -61,8 +61,6 @@
|
|||
#pragma comment(lib, "openal32.lib")
|
||||
#endif
|
||||
|
||||
DEFINE_EXCEPTION(OpenALException, agi::AudioPlayerOpenError);
|
||||
|
||||
namespace {
|
||||
class OpenALPlayer final : public AudioPlayer, wxTimer {
|
||||
/// Number of OpenAL buffers to use
|
||||
|
@ -108,7 +106,7 @@ protected:
|
|||
void Notify() override;
|
||||
|
||||
public:
|
||||
OpenALPlayer(AudioProvider *provider);
|
||||
OpenALPlayer(agi::AudioProvider *provider);
|
||||
~OpenALPlayer();
|
||||
|
||||
void Play(int64_t start,int64_t count) override;
|
||||
|
@ -122,7 +120,7 @@ public:
|
|||
void SetVolume(double vol) override { volume = vol; }
|
||||
};
|
||||
|
||||
OpenALPlayer::OpenALPlayer(AudioProvider *provider)
|
||||
OpenALPlayer::OpenALPlayer(agi::AudioProvider *provider)
|
||||
: AudioPlayer(provider)
|
||||
, samplerate(provider->GetSampleRate())
|
||||
, bpf(provider->GetChannels() * provider->GetBytesPerSample())
|
||||
|
@ -130,25 +128,25 @@ OpenALPlayer::OpenALPlayer(AudioProvider *provider)
|
|||
try {
|
||||
// Open device
|
||||
device = alcOpenDevice(nullptr);
|
||||
if (!device) throw OpenALException("Failed opening default OpenAL device");
|
||||
if (!device) throw AudioPlayerOpenError("Failed opening default OpenAL device");
|
||||
|
||||
// Create context
|
||||
context = alcCreateContext(device, nullptr);
|
||||
if (!context) throw OpenALException("Failed creating OpenAL context");
|
||||
if (!alcMakeContextCurrent(context)) throw OpenALException("Failed selecting OpenAL context");
|
||||
if (!context) throw AudioPlayerOpenError("Failed creating OpenAL context");
|
||||
if (!alcMakeContextCurrent(context)) throw AudioPlayerOpenError("Failed selecting OpenAL context");
|
||||
|
||||
// Clear error code
|
||||
alGetError();
|
||||
|
||||
// Generate buffers
|
||||
alGenBuffers(num_buffers, buffers);
|
||||
if (alGetError() != AL_NO_ERROR) throw OpenALException("Error generating OpenAL buffers");
|
||||
if (alGetError() != AL_NO_ERROR) throw AudioPlayerOpenError("Error generating OpenAL buffers");
|
||||
|
||||
// Generate source
|
||||
alGenSources(1, &source);
|
||||
if (alGetError() != AL_NO_ERROR) {
|
||||
alDeleteBuffers(num_buffers, buffers);
|
||||
throw OpenALException("Error generating OpenAL source");
|
||||
throw AudioPlayerOpenError("Error generating OpenAL source");
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
|
@ -284,7 +282,7 @@ int64_t OpenALPlayer::GetCurrentPosition()
|
|||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioPlayer> CreateOpenALPlayer(AudioProvider *provider, wxWindow *)
|
||||
std::unique_ptr<AudioPlayer> CreateOpenALPlayer(agi::AudioProvider *provider, wxWindow *)
|
||||
{
|
||||
return agi::make_unique<OpenALPlayer>(provider);
|
||||
}
|
||||
|
|
|
@ -35,10 +35,10 @@
|
|||
|
||||
#include "audio_controller.h"
|
||||
#include "compat.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
#include "options.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <libaegisub/audio/provider.h>
|
||||
#include <libaegisub/log.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
|
@ -54,7 +54,6 @@
|
|||
#endif
|
||||
|
||||
namespace {
|
||||
DEFINE_EXCEPTION(OSSError, agi::AudioPlayerOpenError);
|
||||
class OSSPlayerThread;
|
||||
|
||||
class OSSPlayer final : public AudioPlayer {
|
||||
|
@ -90,7 +89,7 @@ class OSSPlayer final : public AudioPlayer {
|
|||
void OpenStream();
|
||||
|
||||
public:
|
||||
OSSPlayer(AudioProvider *provider)
|
||||
OSSPlayer(agi::AudioProvider *provider)
|
||||
: AudioPlayer(provider)
|
||||
{
|
||||
OpenStream();
|
||||
|
@ -153,7 +152,7 @@ void OSSPlayer::OpenStream()
|
|||
wxString device = to_wx(OPT_GET("Player/Audio/OSS/Device")->GetString());
|
||||
dspdev = ::open(device.utf8_str(), O_WRONLY, 0);
|
||||
if (dspdev < 0) {
|
||||
throw OSSError("OSS player: opening device failed");
|
||||
throw AudioPlayerOpenError("OSS player: opening device failed");
|
||||
}
|
||||
|
||||
// Use a reasonable buffer policy for low latency (OSS4)
|
||||
|
@ -165,7 +164,7 @@ void OSSPlayer::OpenStream()
|
|||
// Set number of channels
|
||||
int channels = provider->GetChannels();
|
||||
if (ioctl(dspdev, SNDCTL_DSP_CHANNELS, &channels) < 0) {
|
||||
throw OSSError("OSS player: setting channels failed");
|
||||
throw AudioPlayerOpenError("OSS player: setting channels failed");
|
||||
}
|
||||
|
||||
// Set sample format
|
||||
|
@ -178,17 +177,17 @@ void OSSPlayer::OpenStream()
|
|||
sample_format = AFMT_S16_LE;
|
||||
break;
|
||||
default:
|
||||
throw OSSError("OSS player: can only handle 8 and 16 bit sound");
|
||||
throw AudioPlayerOpenError("OSS player: can only handle 8 and 16 bit sound");
|
||||
}
|
||||
|
||||
if (ioctl(dspdev, SNDCTL_DSP_SETFMT, &sample_format) < 0) {
|
||||
throw OSSError("OSS player: setting sample format failed");
|
||||
throw AudioPlayerOpenError("OSS player: setting sample format failed");
|
||||
}
|
||||
|
||||
// Set sample rate
|
||||
rate = provider->GetSampleRate();
|
||||
if (ioctl(dspdev, SNDCTL_DSP_SPEED, &rate) < 0) {
|
||||
throw OSSError("OSS player: setting samplerate failed");
|
||||
throw AudioPlayerOpenError("OSS player: setting samplerate failed");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -280,7 +279,7 @@ int64_t OSSPlayer::GetCurrentPosition()
|
|||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioPlayer> CreateOSSPlayer(AudioProvider *provider, wxWindow *) {
|
||||
std::unique_ptr<AudioPlayer> CreateOSSPlayer(agi::AudioProvider *provider, wxWindow *) {
|
||||
return agi::make_unique<OSSPlayer>(provider);
|
||||
}
|
||||
|
||||
|
|
|
@ -37,15 +37,13 @@
|
|||
|
||||
#include "audio_controller.h"
|
||||
#include "compat.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
#include "options.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <libaegisub/audio/provider.h>
|
||||
#include <libaegisub/log.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
DEFINE_EXCEPTION(PortAudioError, agi::AudioPlayerOpenError);
|
||||
|
||||
// Uncomment to enable extremely spammy debug logging
|
||||
//#define PORTAUDIO_DEBUG
|
||||
|
||||
|
@ -66,11 +64,11 @@ static const PaHostApiTypeId pa_host_api_priority[] = {
|
|||
};
|
||||
static const size_t pa_host_api_priority_count = sizeof(pa_host_api_priority) / sizeof(pa_host_api_priority[0]);
|
||||
|
||||
PortAudioPlayer::PortAudioPlayer(AudioProvider *provider) : AudioPlayer(provider) {
|
||||
PortAudioPlayer::PortAudioPlayer(agi::AudioProvider *provider) : AudioPlayer(provider) {
|
||||
PaError err = Pa_Initialize();
|
||||
|
||||
if (err != paNoError)
|
||||
throw PortAudioError(std::string("Failed opening PortAudio: ") + Pa_GetErrorText(err));
|
||||
throw AudioPlayerOpenError(std::string("Failed opening PortAudio: ") + Pa_GetErrorText(err));
|
||||
|
||||
// Build a list of host API-specific devices we can use
|
||||
// Some host APIs may not support all audio formats, so build a priority
|
||||
|
@ -83,7 +81,7 @@ PortAudioPlayer::PortAudioPlayer(AudioProvider *provider) : AudioPlayer(provider
|
|||
GatherDevices(Pa_GetDefaultHostApi());
|
||||
|
||||
if (devices.empty())
|
||||
throw PortAudioError("No PortAudio output devices found");
|
||||
throw AudioPlayerOpenError("No PortAudio output devices found");
|
||||
|
||||
if (provider)
|
||||
OpenStream();
|
||||
|
@ -168,7 +166,7 @@ void PortAudioPlayer::OpenStream() {
|
|||
}
|
||||
}
|
||||
|
||||
throw PortAudioError("Failed initializing PortAudio stream: " + error);
|
||||
throw AudioPlayerOpenError("Failed initializing PortAudio stream: " + error);
|
||||
}
|
||||
|
||||
void PortAudioPlayer::paStreamFinishedCallback(void *) {
|
||||
|
@ -270,7 +268,7 @@ wxArrayString PortAudioPlayer::GetOutputDevices() {
|
|||
for (auto it = player.devices.begin(); it != player.devices.end(); ++it)
|
||||
list.push_back(to_wx(it->first));
|
||||
}
|
||||
catch (PortAudioError const&) {
|
||||
catch (AudioPlayerOpenError const&) {
|
||||
// No output devices, just return the list with only Default
|
||||
}
|
||||
|
||||
|
@ -281,7 +279,7 @@ bool PortAudioPlayer::IsPlaying() {
|
|||
return !!Pa_IsStreamActive(stream);
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioPlayer> CreatePortAudioPlayer(AudioProvider *provider, wxWindow *) {
|
||||
std::unique_ptr<AudioPlayer> CreatePortAudioPlayer(agi::AudioProvider *provider, wxWindow *) {
|
||||
return agi::make_unique<PortAudioPlayer>(provider);
|
||||
}
|
||||
|
||||
|
|
|
@ -36,9 +36,9 @@
|
|||
#include "include/aegisub/audio_player.h"
|
||||
|
||||
#include "audio_controller.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <libaegisub/audio/provider.h>
|
||||
#include <libaegisub/log.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
|
@ -83,7 +83,7 @@ class PulseAudioPlayer final : public AudioPlayer {
|
|||
static void pa_stream_notify(pa_stream *p, PulseAudioPlayer *thread);
|
||||
|
||||
public:
|
||||
PulseAudioPlayer(AudioProvider *provider);
|
||||
PulseAudioPlayer(agi::AudioProvider *provider);
|
||||
~PulseAudioPlayer();
|
||||
|
||||
void Play(int64_t start,int64_t count);
|
||||
|
@ -97,11 +97,11 @@ public:
|
|||
void SetVolume(double vol) { volume = vol; }
|
||||
};
|
||||
|
||||
PulseAudioPlayer::PulseAudioPlayer(AudioProvider *provider) : AudioPlayer(provider) {
|
||||
PulseAudioPlayer::PulseAudioPlayer(agi::AudioProvider *provider) : AudioPlayer(provider) {
|
||||
// Initialise a mainloop
|
||||
mainloop = pa_threaded_mainloop_new();
|
||||
if (!mainloop)
|
||||
throw agi::AudioPlayerOpenError("Failed to initialise PulseAudio threaded mainloop object");
|
||||
throw AudioPlayerOpenError("Failed to initialise PulseAudio threaded mainloop object");
|
||||
|
||||
pa_threaded_mainloop_start(mainloop);
|
||||
|
||||
|
@ -109,7 +109,7 @@ PulseAudioPlayer::PulseAudioPlayer(AudioProvider *provider) : AudioPlayer(provid
|
|||
context = pa_context_new(pa_threaded_mainloop_get_api(mainloop), "Aegisub");
|
||||
if (!context) {
|
||||
pa_threaded_mainloop_free(mainloop);
|
||||
throw agi::AudioPlayerOpenError("Failed to create PulseAudio context");
|
||||
throw AudioPlayerOpenError("Failed to create PulseAudio context");
|
||||
}
|
||||
pa_context_set_state_callback(context, (pa_context_notify_cb_t)pa_context_notify, this);
|
||||
|
||||
|
@ -127,7 +127,7 @@ PulseAudioPlayer::PulseAudioPlayer(AudioProvider *provider) : AudioPlayer(provid
|
|||
pa_context_unref(context);
|
||||
pa_threaded_mainloop_stop(mainloop);
|
||||
pa_threaded_mainloop_free(mainloop);
|
||||
throw agi::AudioPlayerOpenError(std::string("PulseAudio reported error: ") + pa_strerror(paerror));
|
||||
throw AudioPlayerOpenError(std::string("PulseAudio reported error: ") + pa_strerror(paerror));
|
||||
}
|
||||
// otherwise loop once more
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ PulseAudioPlayer::PulseAudioPlayer(AudioProvider *provider) : AudioPlayer(provid
|
|||
pa_context_unref(context);
|
||||
pa_threaded_mainloop_stop(mainloop);
|
||||
pa_threaded_mainloop_free(mainloop);
|
||||
throw agi::AudioPlayerOpenError("PulseAudio could not create stream");
|
||||
throw AudioPlayerOpenError("PulseAudio could not create stream");
|
||||
}
|
||||
pa_stream_set_state_callback(stream, (pa_stream_notify_cb_t)pa_stream_notify, this);
|
||||
pa_stream_set_write_callback(stream, (pa_stream_request_cb_t)pa_stream_write, this);
|
||||
|
@ -157,7 +157,7 @@ PulseAudioPlayer::PulseAudioPlayer(AudioProvider *provider) : AudioPlayer(provid
|
|||
paerror = pa_stream_connect_playback(stream, nullptr, nullptr, (pa_stream_flags_t)(PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_NOT_MONOTONOUS|PA_STREAM_AUTO_TIMING_UPDATE), nullptr, nullptr);
|
||||
if (paerror) {
|
||||
LOG_E("audio/player/pulse") << "Stream connection failed: " << pa_strerror(paerror) << "(" << paerror << ")";
|
||||
throw agi::AudioPlayerOpenError(std::string("PulseAudio reported error: ") + pa_strerror(paerror));
|
||||
throw AudioPlayerOpenError(std::string("PulseAudio reported error: ") + pa_strerror(paerror));
|
||||
}
|
||||
while (true) {
|
||||
stream_notify.Wait();
|
||||
|
@ -166,7 +166,7 @@ PulseAudioPlayer::PulseAudioPlayer(AudioProvider *provider) : AudioPlayer(provid
|
|||
} else if (sstate == PA_STREAM_FAILED) {
|
||||
paerror = pa_context_errno(context);
|
||||
LOG_E("audio/player/pulse") << "Stream connection failed: " << pa_strerror(paerror) << "(" << paerror << ")";
|
||||
throw agi::AudioPlayerOpenError("PulseAudio player: Something went wrong connecting the stream");
|
||||
throw AudioPlayerOpenError("PulseAudio player: Something went wrong connecting the stream");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -321,7 +321,7 @@ void PulseAudioPlayer::pa_stream_notify(pa_stream *p, PulseAudioPlayer *thread)
|
|||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioPlayer> CreatePulseAudioPlayer(AudioProvider *provider, wxWindow *) {
|
||||
std::unique_ptr<AudioPlayer> CreatePulseAudioPlayer(agi::AudioProvider *provider, wxWindow *) {
|
||||
return agi::make_unique<PulseAudioPlayer>(provider);
|
||||
}
|
||||
#endif // WITH_LIBPULSE
|
||||
|
|
|
@ -1,191 +0,0 @@
|
|||
// Copyright (c) 2005-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/
|
||||
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include "audio_controller.h"
|
||||
#include "factory_manager.h"
|
||||
#include "options.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <libaegisub/fs.h>
|
||||
#include <libaegisub/log.h>
|
||||
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
|
||||
void AudioProvider::GetAudioWithVolume(void *buf, int64_t start, int64_t count, double volume) const {
|
||||
GetAudio(buf, start, count);
|
||||
|
||||
if (volume == 1.0) return;
|
||||
if (bytes_per_sample != 2)
|
||||
throw agi::InternalError("GetAudioWithVolume called on unconverted audio stream");
|
||||
|
||||
short *buffer = static_cast<int16_t *>(buf);
|
||||
for (size_t i = 0; i < (size_t)count; ++i)
|
||||
buffer[i] = mid<int>(-0x8000, buffer[i] * volume + 0.5, 0x7FFF);
|
||||
}
|
||||
|
||||
void AudioProvider::ZeroFill(void *buf, int64_t count) const {
|
||||
if (bytes_per_sample == 1)
|
||||
// 8 bit formats are usually unsigned with bias 127
|
||||
memset(buf, 127, count * channels);
|
||||
else
|
||||
// While everything else is signed
|
||||
memset(buf, 0, count * bytes_per_sample * channels);
|
||||
}
|
||||
|
||||
void AudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const {
|
||||
if (start < 0) {
|
||||
ZeroFill(buf, std::min(-start, count));
|
||||
buf = static_cast<char *>(buf) + -start * bytes_per_sample * channels;
|
||||
count += start;
|
||||
start = 0;
|
||||
}
|
||||
|
||||
if (start + count > num_samples) {
|
||||
int64_t zero_count = std::min(count, start + count - num_samples);
|
||||
count -= zero_count;
|
||||
ZeroFill(static_cast<char *>(buf) + count * bytes_per_sample * channels, zero_count);
|
||||
}
|
||||
|
||||
if (count <= 0) return;
|
||||
|
||||
try {
|
||||
FillBuffer(buf, start, count);
|
||||
}
|
||||
catch (AudioDecodeError const& e) {
|
||||
LOG_E("audio_provider") << e.GetMessage();
|
||||
ZeroFill(buf, count);
|
||||
return;
|
||||
}
|
||||
catch (...) {
|
||||
// FIXME: Poor error handling though better than none, to patch issue #800.
|
||||
// Just return blank audio if real provider fails.
|
||||
LOG_E("audio_provider") << "Unknown audio decoding error";
|
||||
ZeroFill(buf, count);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioProvider> CreateDummyAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *);
|
||||
std::unique_ptr<AudioProvider> CreatePCMAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *);
|
||||
std::unique_ptr<AudioProvider> CreateAvisynthAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *);
|
||||
std::unique_ptr<AudioProvider> CreateFFmpegSourceAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *);
|
||||
|
||||
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);
|
||||
std::unique_ptr<AudioProvider> CreateRAMAudioProvider(std::unique_ptr<AudioProvider> source_provider);
|
||||
|
||||
namespace {
|
||||
struct factory {
|
||||
const char *name;
|
||||
std::unique_ptr<AudioProvider> (*create)(agi::fs::path const&, agi::BackgroundRunner *);
|
||||
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() {
|
||||
return ::GetClasses(boost::make_iterator_range(std::begin(providers), std::end(providers)));
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioProvider> AudioProviderFactory::GetProvider(agi::fs::path const& filename, agi::BackgroundRunner *br) {
|
||||
auto preferred = OPT_GET("Audio/Provider")->GetString();
|
||||
auto sorted = GetSorted(boost::make_iterator_range(std::begin(providers), std::end(providers)), preferred);
|
||||
|
||||
std::unique_ptr<AudioProvider> provider;
|
||||
bool found_file = false;
|
||||
bool found_audio = false;
|
||||
std::string msg_all; // error messages from all attempted providers
|
||||
std::string msg_partial; // error messages from providers that could partially load the file (knows container, missing codec)
|
||||
|
||||
for (auto const& factory : sorted) {
|
||||
try {
|
||||
provider = factory->create(filename, br);
|
||||
if (!provider) continue;
|
||||
LOG_I("audio_provider") << "Using audio provider: " << factory->name;
|
||||
break;
|
||||
}
|
||||
catch (agi::fs::FileNotFound const& err) {
|
||||
LOG_D("audio_provider") << err.GetMessage();
|
||||
msg_all += std::string(factory->name) + ": " + err.GetMessage() + " not found.\n";
|
||||
}
|
||||
catch (agi::AudioDataNotFoundError const& err) {
|
||||
LOG_D("audio_provider") << err.GetMessage();
|
||||
found_file = true;
|
||||
msg_all += std::string(factory->name) + ": " + err.GetMessage() + "\n";
|
||||
}
|
||||
catch (agi::AudioOpenError const& err) {
|
||||
LOG_D("audio_provider") << err.GetMessage();
|
||||
found_audio = true;
|
||||
found_file = true;
|
||||
std::string thismsg = std::string(factory->name) + ": " + err.GetMessage() + "\n";
|
||||
msg_all += thismsg;
|
||||
msg_partial += thismsg;
|
||||
}
|
||||
}
|
||||
|
||||
if (!provider) {
|
||||
if (found_audio)
|
||||
throw agi::AudioProviderOpenError(msg_partial);
|
||||
if (found_file)
|
||||
throw agi::AudioDataNotFoundError(msg_all);
|
||||
throw agi::fs::FileNotFound(filename);
|
||||
}
|
||||
|
||||
bool needsCache = provider->NeedsCache();
|
||||
|
||||
// Give it a converter if needed
|
||||
if (provider->GetBytesPerSample() != 2 || provider->GetSampleRate() < 32000 || provider->GetChannels() != 1)
|
||||
provider = CreateConvertAudioProvider(std::move(provider));
|
||||
|
||||
// Change provider to RAM/HD cache if needed
|
||||
int cache = OPT_GET("Audio/Cache/Type")->GetInt();
|
||||
if (!cache || !needsCache)
|
||||
return CreateLockAudioProvider(std::move(provider));
|
||||
|
||||
// Convert to RAM
|
||||
if (cache == 1) return CreateRAMAudioProvider(std::move(provider));
|
||||
|
||||
// Convert to HD
|
||||
if (cache == 2) return CreateHDAudioProvider(std::move(provider));
|
||||
|
||||
throw agi::AudioCacheOpenError("Unknown caching method");
|
||||
}
|
|
@ -33,7 +33,7 @@
|
|||
///
|
||||
|
||||
#ifdef WITH_AVISYNTH
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
#include <libaegisub/audio/provider.h>
|
||||
|
||||
#include "avisynth.h"
|
||||
#include "avisynth_wrap.h"
|
||||
|
@ -50,7 +50,7 @@
|
|||
#include <mutex>
|
||||
|
||||
namespace {
|
||||
class AvisynthAudioProvider final : public AudioProvider {
|
||||
class AvisynthAudioProvider final : public agi::AudioProvider {
|
||||
AviSynthWrapper avs_wrapper;
|
||||
PClip clip;
|
||||
|
||||
|
|
|
@ -1,88 +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/
|
||||
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
/*
|
||||
* scheme ::= "dummy-audio" ":" signal-specifier "?" signal-parameters
|
||||
* signal-specifier ::= "silence" | "noise" | "sine" "/" frequency
|
||||
* frequency ::= integer
|
||||
* signal-parameters ::= signal-parameter [ "&" signal-parameters ]
|
||||
* signal-parameter ::= signal-parameter-name "=" integer
|
||||
* signal-parameter-name ::= "sr" | "bd" | "ch" | "ln"
|
||||
*
|
||||
* Signal types:
|
||||
* "silence", a silent signal is generated.
|
||||
* "noise", a white noise signal is generated.
|
||||
* "sine", a sine wave is generated at the specified frequency.
|
||||
*
|
||||
* Signal parameters:
|
||||
* "sr", sample rate to generate signal at.
|
||||
* "bd", bit depth to generate signal at (usually 16).
|
||||
* "ch", number of channels to generate, usually 1 or 2. The same signal is generated
|
||||
* in every channel even if one would be LFE.
|
||||
* "ln", length of signal in samples. ln/sr gives signal length in seconds.
|
||||
*/
|
||||
|
||||
namespace {
|
||||
class DummyAudioProvider final : public AudioProvider {
|
||||
bool noise;
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count) const override {
|
||||
if (noise) {
|
||||
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;
|
||||
decoded_samples = num_samples = (int64_t)5*30*60*1000 * sample_rate / 1000;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioProvider> CreateDummyAudioProvider(agi::fs::path const& file, agi::BackgroundRunner *) {
|
||||
if (!boost::starts_with(file.string(), "dummy-audio:"))
|
||||
return {};
|
||||
return agi::make_unique<DummyAudioProvider>(file);
|
||||
}
|
126
src/audio_provider_factory.cpp
Normal file
126
src/audio_provider_factory.cpp
Normal file
|
@ -0,0 +1,126 @@
|
|||
// 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 "audio_provider_factory.h"
|
||||
|
||||
#include "factory_manager.h"
|
||||
#include "options.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <libaegisub/audio/provider.h>
|
||||
#include <libaegisub/fs.h>
|
||||
#include <libaegisub/log.h>
|
||||
#include <libaegisub/path.h>
|
||||
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
|
||||
using namespace agi;
|
||||
|
||||
std::unique_ptr<AudioProvider> CreateAvisynthAudioProvider(fs::path const& filename, BackgroundRunner *);
|
||||
std::unique_ptr<AudioProvider> CreateFFmpegSourceAudioProvider(fs::path const& filename, BackgroundRunner *);
|
||||
|
||||
namespace {
|
||||
struct factory {
|
||||
const char *name;
|
||||
std::unique_ptr<AudioProvider> (*create)(fs::path const&, BackgroundRunner *);
|
||||
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> GetAudioProviderNames() {
|
||||
return ::GetClasses(boost::make_iterator_range(std::begin(providers), std::end(providers)));
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioProvider> GetAudioProvider(fs::path const& filename, BackgroundRunner *br) {
|
||||
auto preferred = OPT_GET("Audio/Provider")->GetString();
|
||||
auto sorted = GetSorted(boost::make_iterator_range(std::begin(providers), std::end(providers)), preferred);
|
||||
|
||||
std::unique_ptr<AudioProvider> provider;
|
||||
bool found_file = false;
|
||||
bool found_audio = false;
|
||||
std::string msg_all; // error messages from all attempted providers
|
||||
std::string msg_partial; // error messages from providers that could partially load the file (knows container, missing codec)
|
||||
|
||||
for (auto const& factory : sorted) {
|
||||
try {
|
||||
provider = factory->create(filename, br);
|
||||
if (!provider) continue;
|
||||
LOG_I("audio_provider") << "Using audio provider: " << factory->name;
|
||||
break;
|
||||
}
|
||||
catch (fs::FileNotFound const& err) {
|
||||
LOG_D("audio_provider") << err.GetMessage();
|
||||
msg_all += std::string(factory->name) + ": " + err.GetMessage() + " not found.\n";
|
||||
}
|
||||
catch (AudioDataNotFound const& err) {
|
||||
LOG_D("audio_provider") << err.GetMessage();
|
||||
found_file = true;
|
||||
msg_all += std::string(factory->name) + ": " + err.GetMessage() + "\n";
|
||||
}
|
||||
catch (AudioProviderError const& err) {
|
||||
LOG_D("audio_provider") << err.GetMessage();
|
||||
found_audio = true;
|
||||
found_file = true;
|
||||
std::string thismsg = std::string(factory->name) + ": " + err.GetMessage() + "\n";
|
||||
msg_all += thismsg;
|
||||
msg_partial += thismsg;
|
||||
}
|
||||
}
|
||||
|
||||
if (!provider) {
|
||||
if (found_audio)
|
||||
throw AudioProviderError(msg_partial);
|
||||
if (found_file)
|
||||
throw AudioDataNotFound(msg_all);
|
||||
throw fs::FileNotFound(filename);
|
||||
}
|
||||
|
||||
bool needs_cache = provider->NeedsCache();
|
||||
|
||||
// Give it a converter if needed
|
||||
if (provider->GetBytesPerSample() != 2 || provider->GetSampleRate() < 32000 || provider->GetChannels() != 1)
|
||||
provider = CreateConvertAudioProvider(std::move(provider));
|
||||
|
||||
// Change provider to RAM/HD cache if needed
|
||||
int cache = OPT_GET("Audio/Cache/Type")->GetInt();
|
||||
if (!cache || !needs_cache)
|
||||
return CreateLockAudioProvider(std::move(provider));
|
||||
|
||||
// Convert to RAM
|
||||
if (cache == 1) return CreateRAMAudioProvider(std::move(provider));
|
||||
|
||||
// Convert to HD
|
||||
if (cache == 2) {
|
||||
auto path = OPT_GET("Audio/Cache/HD/Location")->GetString();
|
||||
if (path == "default")
|
||||
path = "?temp";
|
||||
auto cache_dir = config::path->MakeAbsolute(config::path->Decode(path), "?temp");
|
||||
return CreateHDAudioProvider(std::move(provider), cache_dir);
|
||||
}
|
||||
|
||||
throw InternalError("Invalid audio caching method");
|
||||
}
|
27
src/audio_provider_factory.h
Normal file
27
src/audio_provider_factory.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
// 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 <libaegisub/fs_fwd.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace agi {
|
||||
class AudioProvider;
|
||||
class BackgroundRunner;
|
||||
}
|
||||
|
||||
std::unique_ptr<agi::AudioProvider> GetAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *br);
|
||||
std::vector<std::string> GetAudioProviderNames();
|
|
@ -33,9 +33,8 @@
|
|||
///
|
||||
|
||||
#ifdef WITH_FFMS2
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
#include <libaegisub/audio/provider.h>
|
||||
|
||||
#include "audio_controller.h"
|
||||
#include "ffmpegsource_common.h"
|
||||
#include "options.h"
|
||||
|
||||
|
@ -45,7 +44,7 @@
|
|||
#include <map>
|
||||
|
||||
namespace {
|
||||
class FFmpegSourceAudioProvider final : public AudioProvider, FFmpegSourceProvider {
|
||||
class FFmpegSourceAudioProvider final : public agi::AudioProvider, FFmpegSourceProvider {
|
||||
/// audio source object
|
||||
agi::scoped_holder<FFMS_AudioSource*, void (FFMS_CC *)(FFMS_AudioSource*)> AudioSource;
|
||||
|
||||
|
@ -55,7 +54,7 @@ class FFmpegSourceAudioProvider final : public AudioProvider, FFmpegSourceProvid
|
|||
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);
|
||||
throw agi::AudioDecodeError(std::string("Failed to get audio samples: ") + ErrInfo.Buffer);
|
||||
}
|
||||
|
||||
public:
|
||||
|
@ -79,7 +78,7 @@ FFmpegSourceAudioProvider::FFmpegSourceAudioProvider(agi::fs::path const& filena
|
|||
LoadAudio(filename);
|
||||
}
|
||||
catch (agi::EnvironmentError const& err) {
|
||||
throw agi::AudioProviderOpenError(err.GetMessage());
|
||||
throw agi::AudioProviderError(err.GetMessage());
|
||||
}
|
||||
|
||||
void FFmpegSourceAudioProvider::LoadAudio(agi::fs::path const& filename) {
|
||||
|
@ -88,12 +87,12 @@ void FFmpegSourceAudioProvider::LoadAudio(agi::fs::path const& filename) {
|
|||
if (ErrInfo.SubType == FFMS_ERROR_FILE_READ)
|
||||
throw agi::fs::FileNotFound(std::string(ErrInfo.Buffer));
|
||||
else
|
||||
throw agi::AudioDataNotFoundError(ErrInfo.Buffer);
|
||||
throw agi::AudioDataNotFound(ErrInfo.Buffer);
|
||||
}
|
||||
|
||||
std::map<int, std::string> TrackList = GetTracksOfType(Indexer, FFMS_TYPE_AUDIO);
|
||||
if (TrackList.empty())
|
||||
throw agi::AudioDataNotFoundError("no audio tracks found");
|
||||
throw agi::AudioDataNotFound("no audio tracks found");
|
||||
|
||||
// initialize the track number to an invalid value so we can detect later on
|
||||
// whether the user actually had to choose a track or not
|
||||
|
@ -121,7 +120,7 @@ void FFmpegSourceAudioProvider::LoadAudio(agi::fs::path const& filename) {
|
|||
if (TrackNumber < 0)
|
||||
TrackNumber = FFMS_GetFirstTrackOfType(Index, FFMS_TYPE_AUDIO, &ErrInfo);
|
||||
if (TrackNumber < 0)
|
||||
throw agi::AudioDataNotFoundError(std::string("Couldn't find any audio tracks: ") + ErrInfo.Buffer);
|
||||
throw agi::AudioDataNotFound(std::string("Couldn't find any audio tracks: ") + ErrInfo.Buffer);
|
||||
|
||||
// index is valid and track number is now set,
|
||||
// but do we have indexing info for the desired audio track?
|
||||
|
@ -163,7 +162,7 @@ void FFmpegSourceAudioProvider::LoadAudio(agi::fs::path const& filename) {
|
|||
|
||||
AudioSource = FFMS_CreateAudioSource(filename.string().c_str(), TrackNumber, Index, -1, &ErrInfo);
|
||||
if (!AudioSource)
|
||||
throw agi::AudioProviderOpenError(std::string("Failed to open audio track: ") + ErrInfo.Buffer);
|
||||
throw agi::AudioProviderError(std::string("Failed to open audio track: ") + ErrInfo.Buffer);
|
||||
|
||||
const FFMS_AudioProperties AudioInfo = *FFMS_GetAudioProperties(AudioSource);
|
||||
|
||||
|
@ -172,7 +171,7 @@ void FFmpegSourceAudioProvider::LoadAudio(agi::fs::path const& filename) {
|
|||
num_samples = AudioInfo.NumSamples;
|
||||
decoded_samples = AudioInfo.NumSamples;
|
||||
if (channels <= 0 || sample_rate <= 0 || num_samples <= 0)
|
||||
throw agi::AudioProviderOpenError("sanity check failed, consult your local psychiatrist");
|
||||
throw agi::AudioProviderError("sanity check failed, consult your local psychiatrist");
|
||||
|
||||
switch (AudioInfo.SampleFormat) {
|
||||
case FFMS_FMT_U8: bytes_per_sample = 1; float_samples = false; break;
|
||||
|
@ -181,7 +180,7 @@ void FFmpegSourceAudioProvider::LoadAudio(agi::fs::path const& filename) {
|
|||
case FFMS_FMT_FLT: bytes_per_sample = 4; float_samples = true; break;
|
||||
case FFMS_FMT_DBL: bytes_per_sample = 8; float_samples = true; break;
|
||||
default:
|
||||
throw agi::AudioProviderOpenError("unknown or unsupported sample format");
|
||||
throw agi::AudioProviderError("unknown or unsupported sample format");
|
||||
}
|
||||
|
||||
#if FFMS_VERSION >= ((2 << 24) | (17 << 16) | (4 << 8) | 0)
|
||||
|
@ -203,7 +202,7 @@ void FFmpegSourceAudioProvider::LoadAudio(agi::fs::path const& filename) {
|
|||
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioProvider> CreateFFmpegSourceAudioProvider(agi::fs::path const& file, agi::BackgroundRunner *br) {
|
||||
std::unique_ptr<agi::AudioProvider> CreateFFmpegSourceAudioProvider(agi::fs::path const& file, agi::BackgroundRunner *br) {
|
||||
return agi::make_unique<FFmpegSourceAudioProvider>(file, br);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,375 +0,0 @@
|
|||
// Copyright (c) 2007-2008, 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/
|
||||
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include "audio_controller.h"
|
||||
|
||||
#include <libaegisub/file_mapping.h>
|
||||
#include <libaegisub/fs.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
class PCMAudioProvider : public AudioProvider {
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count) const override;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<agi::read_file_mapping> file;
|
||||
|
||||
PCMAudioProvider(agi::fs::path const& filename)
|
||||
: file(agi::make_unique<agi::read_file_mapping>(filename))
|
||||
{
|
||||
float_samples = false;
|
||||
}
|
||||
|
||||
const char *EnsureRangeAccessible(int64_t start, int64_t length) const {
|
||||
try {
|
||||
return file->read(start, static_cast<size_t>(length));
|
||||
}
|
||||
catch (agi::fs::FileSystemError const& e) {
|
||||
throw AudioDecodeError(e.GetMessage());
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T const& Read(int64_t start) const {
|
||||
return *reinterpret_cast<const T *>(EnsureRangeAccessible(start, sizeof(T)));
|
||||
}
|
||||
|
||||
struct IndexPoint {
|
||||
int64_t start_byte;
|
||||
int64_t num_samples;
|
||||
};
|
||||
std::vector<IndexPoint> index_points;
|
||||
};
|
||||
|
||||
void PCMAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const {
|
||||
auto write_buf = static_cast<char *>(buf);
|
||||
auto bps = bytes_per_sample * channels;
|
||||
int64_t pos = 0;
|
||||
|
||||
for (auto const& ip : index_points) {
|
||||
if (count == 0) break;
|
||||
if (pos + ip.num_samples < start) {
|
||||
pos += ip.num_samples;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto read_offset = start - pos;
|
||||
auto read_count = std::min(count, ip.num_samples - read_offset);
|
||||
auto bytes = read_count * bps;
|
||||
memcpy(write_buf, file->read(ip.start_byte + read_offset * bps, bytes), bytes);
|
||||
|
||||
write_buf += bytes;
|
||||
count -= read_count;
|
||||
start += read_count;
|
||||
pos += ip.num_samples;
|
||||
}
|
||||
}
|
||||
|
||||
/// @class RiffWavPCMAudioProvider
|
||||
/// @brief RIFF WAV PCM provider
|
||||
///
|
||||
/// Overview of RIFF WAV: <http://www.sonicspot.com/guide/wavefiles.html>
|
||||
class RiffWavPCMAudioProvider : public PCMAudioProvider {
|
||||
struct ChunkHeader {
|
||||
/// Always "RIFF"
|
||||
char type[4];
|
||||
/// File size minus sizeof(ChunkHeader) (i.e. 8)
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
struct RIFFChunk {
|
||||
ChunkHeader ch;
|
||||
/// Always "WAVE"
|
||||
char format[4];
|
||||
};
|
||||
|
||||
struct fmtChunk {
|
||||
/// compression format used
|
||||
/// We support only PCM (0x1)
|
||||
uint16_t compression;
|
||||
|
||||
/// Number of channels
|
||||
uint16_t channels;
|
||||
|
||||
/// Samples per second
|
||||
uint32_t samplerate;
|
||||
|
||||
/// Bytes per second
|
||||
/// can't always be trusted
|
||||
uint32_t avg_bytes_sec;
|
||||
|
||||
/// Bytes per sample
|
||||
uint16_t block_align;
|
||||
|
||||
/// Bits per sample that are actually used; rest should be ignored
|
||||
uint16_t significant_bits_sample;
|
||||
// Here was supposed to be some more fields but we don't need them
|
||||
// and just skipping by the size of the struct wouldn't be safe
|
||||
// either way, as the fields can depend on the compression.
|
||||
};
|
||||
|
||||
static bool CheckFourcc(const char (&str1)[4], const char (&str2)[5])
|
||||
{
|
||||
return str1[0] == str2[0]
|
||||
&& str1[1] == str2[1]
|
||||
&& str1[2] == str2[2]
|
||||
&& str1[3] == str2[3];
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
RiffWavPCMAudioProvider(agi::fs::path const& filename)
|
||||
: PCMAudioProvider(filename)
|
||||
{
|
||||
// Read header
|
||||
auto const& header = Read<RIFFChunk>(0);
|
||||
|
||||
// Check magic values
|
||||
if (!CheckFourcc(header.ch.type, "RIFF"))
|
||||
throw agi::AudioDataNotFoundError("File is not a RIFF file");
|
||||
if (!CheckFourcc(header.format, "WAVE"))
|
||||
throw agi::AudioDataNotFoundError("File is not a RIFF WAV file");
|
||||
|
||||
// How far into the file we have processed.
|
||||
// Must be incremented by the riff chunk size fields.
|
||||
uint32_t filepos = sizeof(header);
|
||||
// Count how much more data we can have in the entire file
|
||||
// The first 4 bytes are already eaten by the header.format field
|
||||
auto total_data = std::min<uint32_t>(header.ch.size - 4 + filepos, file->size());
|
||||
|
||||
bool got_fmt_header = false;
|
||||
|
||||
// Inherited from AudioProvider
|
||||
num_samples = 0;
|
||||
|
||||
// Continue reading chunks until out of data
|
||||
while (filepos + sizeof(ChunkHeader) < total_data) {
|
||||
auto const& ch = Read<ChunkHeader>(filepos);
|
||||
filepos += sizeof(ch);
|
||||
|
||||
if (CheckFourcc(ch.type, "fmt ")) {
|
||||
if (got_fmt_header) throw agi::AudioProviderOpenError("Invalid file, multiple 'fmt ' chunks");
|
||||
got_fmt_header = true;
|
||||
|
||||
auto const& fmt = Read<fmtChunk>(filepos);
|
||||
|
||||
if (fmt.compression != 1)
|
||||
throw agi::AudioProviderOpenError("Can't use file, not PCM encoding");
|
||||
|
||||
// Set stuff inherited from the AudioProvider class
|
||||
sample_rate = fmt.samplerate;
|
||||
channels = fmt.channels;
|
||||
bytes_per_sample = (fmt.significant_bits_sample + 7) / 8; // round up to nearest whole byte
|
||||
}
|
||||
else if (CheckFourcc(ch.type, "data")) {
|
||||
// This won't pick up 'data' chunks inside 'wavl' chunks
|
||||
// since the 'wavl' chunks wrap those.
|
||||
|
||||
if (!got_fmt_header) throw agi::AudioProviderOpenError("Found 'data' chunk before 'fmt ' chunk, file is invalid.");
|
||||
|
||||
auto samples = std::min(total_data - filepos, ch.size) / bytes_per_sample / channels;
|
||||
index_points.push_back(IndexPoint{filepos, samples});
|
||||
num_samples += samples;
|
||||
}
|
||||
|
||||
// Support wavl (wave list) chunks too?
|
||||
|
||||
// Update counters
|
||||
// Make sure they're word aligned
|
||||
filepos += (ch.size + 1) & ~1;
|
||||
}
|
||||
|
||||
decoded_samples = num_samples;
|
||||
}
|
||||
};
|
||||
|
||||
static const uint8_t w64GuidRIFF[16] = {
|
||||
// {66666972-912E-11CF-A5D6-28DB04C10000}
|
||||
0x72, 0x69, 0x66, 0x66, 0x2E, 0x91, 0xCF, 0x11, 0xA5, 0xD6, 0x28, 0xDB, 0x04, 0xC1, 0x00, 0x00
|
||||
};
|
||||
|
||||
static const uint8_t w64GuidWAVE[16] = {
|
||||
// {65766177-ACF3-11D3-8CD1-00C04F8EDB8A}
|
||||
0x77, 0x61, 0x76, 0x65, 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A
|
||||
};
|
||||
|
||||
static const uint8_t w64Guidfmt[16] = {
|
||||
// {20746D66-ACF3-11D3-8CD1-00C04F8EDB8A}
|
||||
0x66, 0x6D, 0x74, 0x20, 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A
|
||||
};
|
||||
|
||||
static const uint8_t w64Guiddata[16] = {
|
||||
// {61746164-ACF3-11D3-8CD1-00C04F8EDB8A}
|
||||
0x64, 0x61, 0x74, 0x61, 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A
|
||||
};
|
||||
|
||||
/// @class Wave64AudioProvider
|
||||
/// @brief Sony Wave64 audio provider
|
||||
///
|
||||
/// http://www.vcs.de/fileadmin/user_upload/MBS/PDF/Whitepaper/Informations_about_Sony_Wave64.pdf
|
||||
class Wave64AudioProvider final : public PCMAudioProvider {
|
||||
// Here's some copy-paste from the FFmpegSource2 code
|
||||
|
||||
/// http://msdn.microsoft.com/en-us/library/dd757720(VS.85).aspx
|
||||
struct WaveFormatEx {
|
||||
uint16_t wFormatTag;
|
||||
uint16_t nChannels;
|
||||
uint32_t nSamplesPerSec;
|
||||
uint32_t nAvgBytesPerSec;
|
||||
uint16_t nBlockAlign;
|
||||
uint16_t wBitsPerSample;
|
||||
uint16_t cbSize;
|
||||
};
|
||||
|
||||
struct RiffChunk {
|
||||
uint8_t riff_guid[16];
|
||||
uint64_t file_size;
|
||||
uint8_t format_guid[16];
|
||||
};
|
||||
|
||||
struct FormatChunk {
|
||||
uint8_t chunk_guid[16];
|
||||
uint64_t chunk_size;
|
||||
WaveFormatEx format;
|
||||
uint8_t padding[6];
|
||||
};
|
||||
|
||||
struct DataChunk {
|
||||
uint8_t chunk_guid[16];
|
||||
uint64_t chunk_size;
|
||||
};
|
||||
|
||||
bool CheckGuid(const uint8_t *guid1, const uint8_t *guid2) {
|
||||
return memcmp(guid1, guid2, 16) == 0;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
Wave64AudioProvider(agi::fs::path const& filename)
|
||||
: PCMAudioProvider(filename)
|
||||
{
|
||||
size_t smallest_possible_file = sizeof(RiffChunk) + sizeof(FormatChunk) + sizeof(DataChunk);
|
||||
|
||||
if (file->size() < smallest_possible_file)
|
||||
throw agi::AudioDataNotFoundError("File is too small to be a Wave64 file");
|
||||
|
||||
// Read header
|
||||
auto const& header = Read<RiffChunk>(0);
|
||||
|
||||
// Check magic values
|
||||
if (!CheckGuid(header.riff_guid, w64GuidRIFF))
|
||||
throw agi::AudioDataNotFoundError("File is not a Wave64 RIFF file");
|
||||
if (!CheckGuid(header.format_guid, w64GuidWAVE))
|
||||
throw agi::AudioDataNotFoundError("File is not a Wave64 WAVE file");
|
||||
|
||||
// How far into the file we have processed.
|
||||
// Must be incremented by the riff chunk size fields.
|
||||
uint64_t filepos = sizeof(header);
|
||||
// Count how much more data we can have in the entire file
|
||||
auto total_data = std::min<uint64_t>(header.file_size, file->size());
|
||||
|
||||
bool got_fmt_header = false;
|
||||
|
||||
// Inherited from AudioProvider
|
||||
num_samples = 0;
|
||||
|
||||
// Continue reading chunks until out of data
|
||||
while (filepos + 24 < total_data) {
|
||||
uint8_t *chunk_guid = (uint8_t*)EnsureRangeAccessible(filepos, 16);
|
||||
auto chunk_size = std::min(total_data - filepos, Read<uint64_t>(filepos + 16)) - 24;
|
||||
filepos += 24;
|
||||
|
||||
if (CheckGuid(chunk_guid, w64Guidfmt)) {
|
||||
if (got_fmt_header)
|
||||
throw agi::AudioProviderOpenError("Bad file, found more than one 'fmt' chunk");
|
||||
|
||||
auto const& fmt = Read<FormatChunk>(filepos);
|
||||
if (fmt.format.wFormatTag != 1)
|
||||
throw agi::AudioProviderOpenError("File is not uncompressed PCM");
|
||||
|
||||
got_fmt_header = true;
|
||||
// Set stuff inherited from the AudioProvider class
|
||||
sample_rate = fmt.format.nSamplesPerSec;
|
||||
channels = fmt.format.nChannels;
|
||||
bytes_per_sample = (fmt.format.wBitsPerSample + 7) / 8; // round up to nearest whole byte
|
||||
}
|
||||
else if (CheckGuid(chunk_guid, w64Guiddata)) {
|
||||
if (!got_fmt_header)
|
||||
throw agi::AudioProviderOpenError("Found 'data' chunk before 'fmt ' chunk, file is invalid.");
|
||||
|
||||
auto samples = chunk_size / bytes_per_sample / channels;
|
||||
index_points.push_back(IndexPoint{
|
||||
static_cast<int64_t>(filepos),
|
||||
static_cast<int64_t>(samples)});
|
||||
num_samples += samples;
|
||||
}
|
||||
|
||||
// Update counters
|
||||
// Make sure they're 64 bit aligned
|
||||
filepos += (chunk_size + 7) & ~7;
|
||||
}
|
||||
|
||||
decoded_samples = num_samples;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<AudioProvider> CreatePCMAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *) {
|
||||
bool wrong_file_type = true;
|
||||
std::string msg;
|
||||
|
||||
try {
|
||||
return agi::make_unique<RiffWavPCMAudioProvider>(filename);
|
||||
}
|
||||
catch (agi::AudioDataNotFoundError const& err) {
|
||||
msg = "RIFF PCM WAV audio provider: " + err.GetMessage();
|
||||
}
|
||||
catch (agi::AudioProviderOpenError const& err) {
|
||||
wrong_file_type = false;
|
||||
msg = "RIFF PCM WAV audio provider: " + err.GetMessage();
|
||||
}
|
||||
|
||||
try {
|
||||
return agi::make_unique<Wave64AudioProvider>(filename);
|
||||
}
|
||||
catch (agi::AudioDataNotFoundError const& err) {
|
||||
msg += "\nWave64 audio provider: " + err.GetMessage();
|
||||
}
|
||||
catch (agi::AudioProviderOpenError const& err) {
|
||||
wrong_file_type = false;
|
||||
msg += "\nWave64 audio provider: " + err.GetMessage();
|
||||
}
|
||||
|
||||
if (wrong_file_type)
|
||||
throw agi::AudioDataNotFoundError(msg);
|
||||
else
|
||||
throw agi::AudioProviderOpenError(msg);
|
||||
}
|
|
@ -33,8 +33,7 @@
|
|||
|
||||
#include "audio_renderer.h"
|
||||
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include <libaegisub/audio/provider.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
@ -120,7 +119,7 @@ void AudioRenderer::SetRenderer(AudioRendererBitmapProvider *_renderer)
|
|||
}
|
||||
}
|
||||
|
||||
void AudioRenderer::SetAudioProvider(AudioProvider *_provider)
|
||||
void AudioRenderer::SetAudioProvider(agi::AudioProvider *_provider)
|
||||
{
|
||||
if (compare_and_set(provider, _provider))
|
||||
{
|
||||
|
@ -224,7 +223,7 @@ void AudioRenderer::Invalidate()
|
|||
needs_age = false;
|
||||
}
|
||||
|
||||
void AudioRendererBitmapProvider::SetProvider(AudioProvider *_provider)
|
||||
void AudioRendererBitmapProvider::SetProvider(agi::AudioProvider *_provider)
|
||||
{
|
||||
if (compare_and_set(provider, _provider))
|
||||
OnSetProvider();
|
||||
|
|
|
@ -37,10 +37,10 @@
|
|||
#include "audio_rendering_style.h"
|
||||
#include "block_cache.h"
|
||||
|
||||
class AudioProvider;
|
||||
class AudioRendererBitmapProvider;
|
||||
class AudioRenderer;
|
||||
class AudioRendererBitmapProvider;
|
||||
class wxDC;
|
||||
namespace agi { class AudioProvider; }
|
||||
|
||||
/// @class AudioRendererBitmapCacheBitmapFactory
|
||||
/// @brief Produces wxBitmap objects for DataBlockCache storage for the audio renderer
|
||||
|
@ -102,7 +102,7 @@ class AudioRenderer {
|
|||
AudioRendererBitmapProvider *renderer = nullptr;
|
||||
|
||||
/// Audio provider to use as source
|
||||
AudioProvider *provider = nullptr;
|
||||
agi::AudioProvider *provider = nullptr;
|
||||
|
||||
/// @brief Make sure bitmap index i is in cache
|
||||
/// @param i Index of bitmap to get into cache
|
||||
|
@ -191,7 +191,7 @@ public:
|
|||
/// Changing audio provider invalidates all cached bitmaps.
|
||||
///
|
||||
/// If a renderer is set, this will also set the audio provider for the renderer.
|
||||
void SetAudioProvider(AudioProvider *provider);
|
||||
void SetAudioProvider(agi::AudioProvider *provider);
|
||||
|
||||
/// @brief Render audio to a device context
|
||||
/// @param dc The device context to draw to
|
||||
|
@ -223,7 +223,7 @@ public:
|
|||
class AudioRendererBitmapProvider {
|
||||
protected:
|
||||
/// Audio provider to use for rendering
|
||||
AudioProvider *provider;
|
||||
agi::AudioProvider *provider;
|
||||
/// Horizontal zoom in milliseconds per pixel
|
||||
double pixel_ms;
|
||||
/// Vertical zoom/amplitude scale factor
|
||||
|
@ -271,7 +271,7 @@ public:
|
|||
|
||||
/// @brief Change audio provider
|
||||
/// @param provider Audio provider to change to
|
||||
void SetProvider(AudioProvider *provider);
|
||||
void SetProvider(agi::AudioProvider *provider);
|
||||
|
||||
/// @brief Change horizontal zoom
|
||||
/// @param pixel_ms Milliseconds per pixel to zoom to
|
||||
|
|
|
@ -38,8 +38,8 @@
|
|||
#ifndef WITH_FFTW3
|
||||
#include "fft.h"
|
||||
#endif
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include <libaegisub/audio/provider.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
|
|
@ -30,9 +30,10 @@
|
|||
#include "audio_renderer_waveform.h"
|
||||
|
||||
#include "audio_colorscheme.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
#include "options.h"
|
||||
|
||||
#include <libaegisub/audio/provider.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <wx/dcmemory.h>
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
#include "../audio_controller.h"
|
||||
#include "../audio_karaoke.h"
|
||||
#include "../audio_timing.h"
|
||||
#include "../include/aegisub/audio_provider.h"
|
||||
#include "../include/aegisub/context.h"
|
||||
#include "../libresrc/libresrc.h"
|
||||
#include "../options.h"
|
||||
|
@ -46,6 +45,7 @@
|
|||
#include "../utils.h"
|
||||
#include "../video_controller.h"
|
||||
|
||||
#include <libaegisub/audio/provider.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
#include <libaegisub/io.h>
|
||||
|
||||
|
@ -159,29 +159,6 @@ struct audio_view_waveform final : public Command {
|
|||
}
|
||||
};
|
||||
|
||||
class writer {
|
||||
agi::io::Save outfile;
|
||||
std::ostream& out;
|
||||
|
||||
public:
|
||||
writer(agi::fs::path const& filename) : outfile(filename, true), out(outfile.Get()) { }
|
||||
|
||||
template<int N>
|
||||
void write(const char(&str)[N]) {
|
||||
out.write(str, N - 1);
|
||||
}
|
||||
|
||||
void write(std::vector<char> const& data) {
|
||||
out.write(data.data(), data.size());
|
||||
}
|
||||
|
||||
template<typename Dest, typename Src>
|
||||
void write(Src v) {
|
||||
auto converted = static_cast<Dest>(v);
|
||||
out.write(reinterpret_cast<char *>(&converted), sizeof(Dest));
|
||||
}
|
||||
};
|
||||
|
||||
struct audio_save_clip final : public Command {
|
||||
CMD_NAME("audio/save/clip")
|
||||
STR_MENU("Create audio clip")
|
||||
|
@ -206,39 +183,7 @@ struct audio_save_clip final : public Command {
|
|||
end = std::max(end, line->End);
|
||||
}
|
||||
|
||||
auto provider = c->project->AudioProvider();
|
||||
|
||||
auto start_sample = ((int64_t)start * provider->GetSampleRate() + 999) / 1000;
|
||||
auto end_sample = ((int64_t)end * provider->GetSampleRate() + 999) / 1000;
|
||||
if (start_sample >= provider->GetNumSamples() || start_sample >= end_sample) return;
|
||||
|
||||
size_t bytes_per_sample = provider->GetBytesPerSample() * provider->GetChannels();
|
||||
size_t bufsize = (end_sample - start_sample) * bytes_per_sample;
|
||||
|
||||
writer out{filename};
|
||||
out.write("RIFF");
|
||||
out.write<int32_t>(bufsize + 36);
|
||||
|
||||
out.write("WAVEfmt ");
|
||||
out.write<int32_t>(16); // Size of chunk
|
||||
out.write<int16_t>(1); // compression format (PCM)
|
||||
out.write<int16_t>(provider->GetChannels());
|
||||
out.write<int32_t>(provider->GetSampleRate());
|
||||
out.write<int32_t>(provider->GetSampleRate() * provider->GetChannels() * provider->GetBytesPerSample());
|
||||
out.write<int16_t>(provider->GetChannels() * provider->GetBytesPerSample());
|
||||
out.write<int16_t>(provider->GetBytesPerSample() * 8);
|
||||
|
||||
out.write("data");
|
||||
out.write<int32_t>(bufsize);
|
||||
|
||||
// samples per read
|
||||
size_t spr = 65536 / bytes_per_sample;
|
||||
std::vector<char> buf(bufsize);
|
||||
for (int64_t i = start_sample; i < end_sample; i += spr) {
|
||||
buf.resize(std::min<size_t>(spr, end_sample - i) * bytes_per_sample);
|
||||
provider->GetAudio(&buf[0], i, buf.size());
|
||||
out.write(buf);
|
||||
}
|
||||
agi::SaveAudioClip(c->project->AudioProvider(), filename, start, end);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -335,7 +335,7 @@ void FrameMain::OnStatusClear(wxTimerEvent &) {
|
|||
SetStatusText("",1);
|
||||
}
|
||||
|
||||
void FrameMain::OnAudioOpen(AudioProvider *provider) {
|
||||
void FrameMain::OnAudioOpen(agi::AudioProvider *provider) {
|
||||
if (provider)
|
||||
SetDisplayMode(-1, 1);
|
||||
else
|
||||
|
|
|
@ -34,8 +34,8 @@
|
|||
class AegisubApp;
|
||||
class AsyncVideoProvider;
|
||||
class AudioBox;
|
||||
class AudioProvider;
|
||||
class VideoBox;
|
||||
namespace agi { class AudioProvider; }
|
||||
namespace agi { struct Context; class OptionValue; }
|
||||
|
||||
class FrameMain : public wxFrame {
|
||||
|
@ -63,7 +63,7 @@ class FrameMain : public wxFrame {
|
|||
void OnStatusClear(wxTimerEvent &event);
|
||||
void OnCloseWindow (wxCloseEvent &event);
|
||||
|
||||
void OnAudioOpen(AudioProvider *provider);
|
||||
void OnAudioOpen(agi::AudioProvider *provider);
|
||||
void OnVideoOpen(AsyncVideoProvider *provider);
|
||||
void OnVideoDetach(agi::OptionValue const& opt);
|
||||
void OnSubtitlesOpen();
|
||||
|
|
|
@ -34,20 +34,22 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <libaegisub/exception.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class AudioProvider;
|
||||
namespace agi { class AudioProvider; }
|
||||
class wxWindow;
|
||||
|
||||
class AudioPlayer {
|
||||
protected:
|
||||
AudioProvider *provider;
|
||||
agi::AudioProvider *provider;
|
||||
|
||||
public:
|
||||
AudioPlayer(AudioProvider *provider);
|
||||
AudioPlayer(agi::AudioProvider *provider) : provider(provider) { }
|
||||
virtual ~AudioPlayer() = default;
|
||||
|
||||
virtual void Play(int64_t start,int64_t count)=0; // Play sample range
|
||||
|
@ -63,5 +65,7 @@ public:
|
|||
|
||||
struct AudioPlayerFactory {
|
||||
static std::vector<std::string> GetClasses();
|
||||
static std::unique_ptr<AudioPlayer> GetAudioPlayer(AudioProvider *provider, wxWindow *window);
|
||||
static std::unique_ptr<AudioPlayer> GetAudioPlayer(agi::AudioProvider *provider, wxWindow *window);
|
||||
};
|
||||
|
||||
DEFINE_EXCEPTION(AudioPlayerOpenError, agi::Exception);
|
||||
|
|
|
@ -1,99 +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/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libaegisub/exception.h>
|
||||
#include <libaegisub/fs_fwd.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
class AudioProvider {
|
||||
protected:
|
||||
int channels;
|
||||
|
||||
/// for one channel, ie. number of PCM frames
|
||||
int64_t num_samples;
|
||||
std::atomic<int64_t> decoded_samples;
|
||||
int sample_rate;
|
||||
int bytes_per_sample;
|
||||
bool float_samples;
|
||||
|
||||
virtual void FillBuffer(void *buf, int64_t start, int64_t count) const = 0;
|
||||
|
||||
void ZeroFill(void *buf, int64_t count) const;
|
||||
|
||||
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;
|
||||
|
||||
int64_t GetNumSamples() const { return num_samples; }
|
||||
int64_t GetDecodedSamples() const { return decoded_samples; }
|
||||
int GetSampleRate() const { return sample_rate; }
|
||||
int GetBytesPerSample() const { return bytes_per_sample; }
|
||||
int GetChannels() const { return channels; }
|
||||
bool AreSamplesFloat() const { return float_samples; }
|
||||
|
||||
/// @brief Does this provider benefit from external caching?
|
||||
virtual bool NeedsCache() const { return false; }
|
||||
};
|
||||
|
||||
/// Helper base class for an audio provider which wraps another provider
|
||||
class AudioProviderWrapper : public AudioProvider {
|
||||
protected:
|
||||
std::unique_ptr<AudioProvider> source;
|
||||
public:
|
||||
AudioProviderWrapper(std::unique_ptr<AudioProvider> src)
|
||||
: source(std::move(src))
|
||||
{
|
||||
channels = source->GetChannels();
|
||||
num_samples = source->GetNumSamples();
|
||||
decoded_samples = source->GetDecodedSamples();
|
||||
sample_rate = source->GetSampleRate();
|
||||
bytes_per_sample = source->GetBytesPerSample();
|
||||
float_samples = source->AreSamplesFloat();
|
||||
}
|
||||
};
|
||||
|
||||
namespace agi { class BackgroundRunner; }
|
||||
|
||||
struct AudioProviderFactory {
|
||||
static std::vector<std::string> GetClasses();
|
||||
|
||||
/// Get a provider for the file
|
||||
/// @param filename URI to open
|
||||
static std::unique_ptr<AudioProvider> GetProvider(agi::fs::path const& filename, agi::BackgroundRunner *br);
|
||||
};
|
||||
|
||||
DEFINE_EXCEPTION(AudioProviderError, agi::Exception);
|
||||
/// Error of some sort occurred while decoding a frame
|
||||
DEFINE_EXCEPTION(AudioDecodeError, AudioProviderError);
|
|
@ -19,13 +19,13 @@
|
|||
#include "preferences.h"
|
||||
|
||||
#include "ass_style_storage.h"
|
||||
#include "audio_provider_factory.h"
|
||||
#include "audio_renderer_waveform.h"
|
||||
#include "command/command.h"
|
||||
#include "compat.h"
|
||||
#include "help_button.h"
|
||||
#include "hotkey_data_view_model.h"
|
||||
#include "include/aegisub/audio_player.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
#include "include/aegisub/hotkey.h"
|
||||
#include "include/aegisub/subtitles_provider.h"
|
||||
#include "libresrc/libresrc.h"
|
||||
|
@ -356,7 +356,7 @@ void Advanced_Audio(wxTreebook *book, Preferences *parent) {
|
|||
|
||||
auto expert = p->PageSizer(_("Expert"));
|
||||
|
||||
wxArrayString ap_choice = to_wx(AudioProviderFactory::GetClasses());
|
||||
wxArrayString ap_choice = to_wx(GetAudioProviderNames());
|
||||
p->OptionChoice(expert, _("Audio provider"), ap_choice, "Audio/Provider");
|
||||
|
||||
wxArrayString apl_choice = to_wx(AudioPlayerFactory::GetClasses());
|
||||
|
|
|
@ -20,13 +20,13 @@
|
|||
#include "ass_file.h"
|
||||
#include "async_video_provider.h"
|
||||
#include "audio_controller.h"
|
||||
#include "audio_provider_factory.h"
|
||||
#include "base_grid.h"
|
||||
#include "charset_detect.h"
|
||||
#include "compat.h"
|
||||
#include "dialog_progress.h"
|
||||
#include "dialogs.h"
|
||||
#include "format.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
#include "include/aegisub/context.h"
|
||||
#include "include/aegisub/video_provider.h"
|
||||
#include "mkv_wrap.h"
|
||||
|
@ -37,6 +37,7 @@
|
|||
#include "video_controller.h"
|
||||
#include "video_display.h"
|
||||
|
||||
#include <libaegisub/audio/provider.h>
|
||||
#include <libaegisub/format_path.h>
|
||||
#include <libaegisub/fs.h>
|
||||
#include <libaegisub/keyframe.h>
|
||||
|
@ -243,7 +244,7 @@ void Project::DoLoadAudio(agi::fs::path const& path, bool quiet) {
|
|||
|
||||
try {
|
||||
try {
|
||||
audio_provider = AudioProviderFactory::GetProvider(path, progress);
|
||||
audio_provider = GetAudioProvider(path, progress);
|
||||
}
|
||||
catch (agi::UserCancelException const&) { return; }
|
||||
catch (...) {
|
||||
|
@ -254,7 +255,7 @@ void Project::DoLoadAudio(agi::fs::path const& path, bool quiet) {
|
|||
catch (agi::fs::FileNotFound const& e) {
|
||||
return ShowError(_("The audio file was not found: ") + to_wx(e.GetMessage()));
|
||||
}
|
||||
catch (agi::AudioDataNotFoundError const& e) {
|
||||
catch (agi::AudioDataNotFound const& e) {
|
||||
if (quiet) {
|
||||
LOG_D("video/open/audio") << "File " << video_file << " has no audio data: " << e.GetMessage();
|
||||
return;
|
||||
|
@ -262,7 +263,7 @@ void Project::DoLoadAudio(agi::fs::path const& path, bool quiet) {
|
|||
else
|
||||
return ShowError(_("None of the available audio providers recognised the selected file as containing audio data.\n\nThe following providers were tried:\n") + to_wx(e.GetMessage()));
|
||||
}
|
||||
catch (agi::AudioProviderOpenError const& e) {
|
||||
catch (agi::AudioProviderError const& e) {
|
||||
return ShowError(_("None of the available audio providers have a codec available to handle the selected file.\n\nThe following providers were tried:\n") + to_wx(e.GetMessage()));
|
||||
}
|
||||
catch (agi::Exception const& e) {
|
||||
|
|
|
@ -23,14 +23,14 @@
|
|||
#include <vector>
|
||||
|
||||
class AsyncVideoProvider;
|
||||
class AudioProvider;
|
||||
class DialogProgress;
|
||||
class wxString;
|
||||
namespace agi { class AudioProvider; }
|
||||
namespace agi { struct Context; }
|
||||
struct ProjectProperties;
|
||||
|
||||
class Project {
|
||||
std::unique_ptr<::AudioProvider> audio_provider;
|
||||
std::unique_ptr<agi::AudioProvider> audio_provider;
|
||||
std::unique_ptr<AsyncVideoProvider> video_provider;
|
||||
agi::vfr::Framerate timecodes;
|
||||
std::vector<int> keyframes;
|
||||
|
@ -40,7 +40,7 @@ class Project {
|
|||
agi::fs::path timecodes_file;
|
||||
agi::fs::path keyframes_file;
|
||||
|
||||
agi::signal::Signal<::AudioProvider *> AnnounceAudioProviderModified;
|
||||
agi::signal::Signal<agi::AudioProvider *> AnnounceAudioProviderModified;
|
||||
agi::signal::Signal<AsyncVideoProvider *> AnnounceVideoProviderModified;
|
||||
agi::signal::Signal<agi::vfr::Framerate const&> AnnounceTimecodesModified;
|
||||
agi::signal::Signal<std::vector<int> const&> AnnounceKeyframesModified;
|
||||
|
@ -75,7 +75,7 @@ public:
|
|||
|
||||
void LoadAudio(agi::fs::path path);
|
||||
void CloseAudio();
|
||||
::AudioProvider *AudioProvider() const { return audio_provider.get(); }
|
||||
agi::AudioProvider *AudioProvider() const { return audio_provider.get(); }
|
||||
agi::fs::path const& AudioName() const { return audio_file; }
|
||||
|
||||
void LoadVideo(agi::fs::path path);
|
||||
|
|
356
tests/tests/audio.cpp
Normal file
356
tests/tests/audio.cpp
Normal file
|
@ -0,0 +1,356 @@
|
|||
// 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 <main.h>
|
||||
|
||||
#include <libaegisub/audio/provider.h>
|
||||
#include <libaegisub/fs.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
#include <libaegisub/path.h>
|
||||
#include <libaegisub/util.h>
|
||||
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
|
||||
namespace bfs = boost::filesystem;
|
||||
|
||||
TEST(lagi_audio, dummy_blank) {
|
||||
auto provider = agi::CreateDummyAudioProvider("dummy-audio:", nullptr);
|
||||
|
||||
char buff[1024];
|
||||
memset(buff, sizeof(buff), 1);
|
||||
provider->GetAudio(buff, 12356, 512);
|
||||
for (size_t i = 0; i < sizeof(buff); ++i) ASSERT_EQ(0, buff[i]);
|
||||
}
|
||||
|
||||
TEST(lagi_audio, dummy_noise) {
|
||||
auto provider = agi::CreateDummyAudioProvider("dummy-audio:noise?", nullptr);
|
||||
|
||||
char buff[1024];
|
||||
memset(buff, sizeof(buff), 0);
|
||||
provider->GetAudio(buff, 12356, 512);
|
||||
for (size_t i = 0; i < sizeof(buff); ++i) {
|
||||
if (buff[i] != 0)
|
||||
return;
|
||||
}
|
||||
bool all_zero = true;
|
||||
ASSERT_FALSE(all_zero);
|
||||
}
|
||||
|
||||
TEST(lagi_audio, dummy_rejects_non_dummy_url) {
|
||||
auto provider = agi::CreateDummyAudioProvider("/tmp", nullptr);
|
||||
ASSERT_EQ(nullptr, provider.get());
|
||||
}
|
||||
|
||||
struct TestAudioProvider : agi::AudioProvider {
|
||||
TestAudioProvider(int64_t duration = 90) {
|
||||
channels = 1;
|
||||
num_samples = duration * 48000;
|
||||
decoded_samples = num_samples;
|
||||
sample_rate = 48000;
|
||||
bytes_per_sample = 2;
|
||||
float_samples = false;
|
||||
}
|
||||
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count) const override {
|
||||
auto out = static_cast<uint16_t *>(buf);
|
||||
for (int64_t end = start + count; start < end; ++start)
|
||||
*out++ = (uint16_t)start;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(lagi_audio, before_sample_zero) {
|
||||
TestAudioProvider provider;
|
||||
|
||||
uint16_t buff[16];
|
||||
memset(buff, sizeof(buff), 1);
|
||||
provider.GetAudio(buff, -8, 16);
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
ASSERT_EQ(0, buff[i]);
|
||||
for (int i = 8; i < 16; ++i)
|
||||
ASSERT_EQ(i - 8, buff[i]);
|
||||
}
|
||||
|
||||
TEST(lagi_audio, after_end) {
|
||||
TestAudioProvider provider(1);
|
||||
|
||||
uint16_t buff[16];
|
||||
memset(buff, sizeof(buff), 1);
|
||||
provider.GetAudio(buff, provider.GetNumSamples() - 8, 16);
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
ASSERT_NE(0, buff[i]);
|
||||
for (int i = 8; i < 16; ++i)
|
||||
ASSERT_EQ(0, buff[i]);
|
||||
}
|
||||
|
||||
TEST(lagi_audio, save_audio_clip) {
|
||||
auto path = agi::Path().Decode("?temp/save_clip");
|
||||
agi::fs::Remove(path);
|
||||
|
||||
auto provider = agi::CreateDummyAudioProvider("dummy-audio:noise?", nullptr);
|
||||
agi::SaveAudioClip(provider.get(), path, 60 * 60 * 1000, (60 * 60 + 10) * 1000);
|
||||
|
||||
{
|
||||
bfs::ifstream s(path);
|
||||
ASSERT_TRUE(s.good());
|
||||
s.seekg(0, std::ios::end);
|
||||
// 10 seconds of 44.1 kHz samples per second of 16-bit mono, plus 44 bytes of header
|
||||
EXPECT_EQ(10 * 44100 * 2 + 44, s.tellg());
|
||||
}
|
||||
agi::fs::Remove(path);
|
||||
}
|
||||
|
||||
TEST(lagi_audio, get_with_volume) {
|
||||
TestAudioProvider provider;
|
||||
uint16_t buff[4];
|
||||
|
||||
provider.GetAudioWithVolume(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);
|
||||
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);
|
||||
EXPECT_EQ(0, buff[0]);
|
||||
EXPECT_EQ(2, buff[1]);
|
||||
EXPECT_EQ(4, buff[2]);
|
||||
EXPECT_EQ(6, buff[3]);
|
||||
}
|
||||
|
||||
TEST(lagi_audio, volume_should_clamp_rather_than_wrap) {
|
||||
TestAudioProvider provider;
|
||||
uint16_t buff[1];
|
||||
provider.GetAudioWithVolume(buff, 30000, 1, 2.0);
|
||||
EXPECT_EQ(SHRT_MAX, buff[0]);
|
||||
}
|
||||
|
||||
TEST(lagi_audio, ram_cache) {
|
||||
auto provider = agi::CreateRAMAudioProvider(agi::make_unique<TestAudioProvider>());
|
||||
EXPECT_EQ(1, provider->GetChannels());
|
||||
EXPECT_EQ(90 * 48000, provider->GetNumSamples());
|
||||
EXPECT_EQ(48000, provider->GetSampleRate());
|
||||
EXPECT_EQ(2, provider->GetBytesPerSample());
|
||||
EXPECT_EQ(false, provider->AreSamplesFloat());
|
||||
EXPECT_EQ(false, provider->NeedsCache());
|
||||
while (provider->GetDecodedSamples() != provider->GetNumSamples()) agi::util::sleep_for(0);
|
||||
|
||||
uint16_t buff[512];
|
||||
provider->GetAudio(buff, (1 << 22) - 256, 512); // Stride two cache blocks
|
||||
|
||||
for (size_t i = 0; i < 512; ++i)
|
||||
ASSERT_EQ(static_cast<uint16_t>((1 << 22) - 256 + i), buff[i]);
|
||||
}
|
||||
|
||||
TEST(lagi_audio, hd_cache) {
|
||||
auto provider = agi::CreateHDAudioProvider(agi::make_unique<TestAudioProvider>(), agi::Path().Decode("?temp"));
|
||||
while (provider->GetDecodedSamples() != provider->GetNumSamples()) agi::util::sleep_for(0);
|
||||
|
||||
uint16_t buff[512];
|
||||
provider->GetAudio(buff, (1 << 22) - 256, 512);
|
||||
|
||||
for (size_t i = 0; i < 512; ++i)
|
||||
ASSERT_EQ(static_cast<uint16_t>((1 << 22) - 256 + i), buff[i]);
|
||||
}
|
||||
|
||||
TEST(lagi_audio, pcm_simple) {
|
||||
auto path = agi::Path().Decode("?temp/pcm_simple");
|
||||
{
|
||||
TestAudioProvider provider;
|
||||
agi::SaveAudioClip(&provider, path, 0, 1000);
|
||||
}
|
||||
|
||||
auto provider = agi::CreatePCMAudioProvider(path, nullptr);
|
||||
EXPECT_EQ(1, provider->GetChannels());
|
||||
EXPECT_EQ(48000, provider->GetNumSamples());
|
||||
EXPECT_EQ(48000, provider->GetSampleRate());
|
||||
EXPECT_EQ(2, provider->GetBytesPerSample());
|
||||
EXPECT_EQ(false, provider->AreSamplesFloat());
|
||||
EXPECT_EQ(false, provider->NeedsCache());
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
uint16_t sample;
|
||||
provider->GetAudio(&sample, i, 1);
|
||||
ASSERT_EQ(i, sample);
|
||||
}
|
||||
|
||||
agi::fs::Remove(path);
|
||||
}
|
||||
|
||||
TEST(lagi_audio, pcm_truncated) {
|
||||
auto path = agi::Path().Decode("?temp/pcm_truncated");
|
||||
{
|
||||
TestAudioProvider provider;
|
||||
agi::SaveAudioClip(&provider, path, 0, 1000);
|
||||
}
|
||||
|
||||
char file[1000];
|
||||
|
||||
{ bfs::ifstream s(path); s.read(file, sizeof file); }
|
||||
{ bfs::ofstream s(path); s.write(file, sizeof file); }
|
||||
|
||||
auto provider = agi::CreatePCMAudioProvider(path, nullptr);
|
||||
|
||||
// Should still report full duration
|
||||
EXPECT_EQ(48000, provider->GetNumSamples());
|
||||
|
||||
// And should zero-pad past the end
|
||||
auto sample_count = (1000 - 44) / 2;
|
||||
uint16_t sample;
|
||||
|
||||
provider->GetAudio(&sample, sample_count - 1, 1);
|
||||
EXPECT_EQ(sample_count - 1, sample);
|
||||
|
||||
provider->GetAudio(&sample, sample_count, 1);
|
||||
EXPECT_EQ(0, sample);
|
||||
|
||||
agi::fs::Remove(path);
|
||||
}
|
||||
|
||||
#define RIFF "RIFF\0\0\0\x60WAVE"
|
||||
#define FMT_VALID "fmt \20\0\0\0\1\0\1\0\20\0\0\0\0\0\0\0\0\0\20\0"
|
||||
#define DATA_VALID "data\1\0\0\0\0\0"
|
||||
#define WRITE(str) do { bfs::ofstream s(path); s.write(str, sizeof(str) - 1); } while (false)
|
||||
|
||||
TEST(lagi_audio, pcm_incomplete) {
|
||||
auto path = agi::Path().Decode("?temp/pcm_incomplete");
|
||||
|
||||
agi::fs::Remove(path);
|
||||
ASSERT_THROW(agi::CreatePCMAudioProvider(path, nullptr), agi::fs::FileNotFound);
|
||||
|
||||
bfs::ofstream{path};
|
||||
ASSERT_THROW(agi::CreatePCMAudioProvider(path, nullptr), agi::AudioDataNotFound);
|
||||
|
||||
// Invalid tags
|
||||
WRITE("ASDF");
|
||||
ASSERT_THROW(agi::CreatePCMAudioProvider(path, nullptr), agi::AudioDataNotFound);
|
||||
|
||||
WRITE("RIFF\0\0\0\x60" "ASDF");
|
||||
ASSERT_THROW(agi::CreatePCMAudioProvider(path, nullptr), agi::AudioDataNotFound);
|
||||
|
||||
// Incomplete files
|
||||
auto valid_file = RIFF FMT_VALID DATA_VALID;
|
||||
|
||||
// -1 for nul term, -3 so that longest file is still invalid
|
||||
for (size_t i = 0; i < sizeof(valid_file) - 4; ++i) {
|
||||
bfs::ofstream s(path);
|
||||
s.write(valid_file, i);
|
||||
ASSERT_THROW(agi::CreatePCMAudioProvider(path, nullptr), agi::AudioDataNotFound);
|
||||
}
|
||||
|
||||
// fmt must come before data
|
||||
WRITE(RIFF "data\0\0\0\x60");
|
||||
ASSERT_THROW(agi::CreatePCMAudioProvider(path, nullptr), agi::AudioProviderError);
|
||||
|
||||
// Bad compression format
|
||||
WRITE(RIFF "fmt \x60\0\0\0\2\0");
|
||||
ASSERT_THROW(agi::CreatePCMAudioProvider(path, nullptr), agi::AudioProviderError);
|
||||
|
||||
// Multiple fmt chunks not supported
|
||||
WRITE(RIFF FMT_VALID FMT_VALID);
|
||||
ASSERT_THROW(agi::CreatePCMAudioProvider(path, nullptr), agi::AudioProviderError);
|
||||
|
||||
agi::fs::Remove(path);
|
||||
}
|
||||
|
||||
TEST(lagi_audio, multiple_data_chunks) {
|
||||
auto path = agi::Path().Decode("?temp/multiple_data");
|
||||
|
||||
WRITE(RIFF FMT_VALID "data\2\0\0\0\1\0" "data\2\0\0\0\2\0" "data\2\0\0\0\3\0");
|
||||
|
||||
auto provider = agi::CreatePCMAudioProvider(path, nullptr);
|
||||
ASSERT_EQ(3, provider->GetNumSamples());
|
||||
|
||||
uint16_t samples[3];
|
||||
|
||||
provider->GetAudio(samples, 0, 3);
|
||||
EXPECT_EQ(1, samples[0]);
|
||||
EXPECT_EQ(2, samples[1]);
|
||||
EXPECT_EQ(3, samples[2]);
|
||||
|
||||
samples[1] = 5;
|
||||
provider->GetAudio(samples, 2, 1);
|
||||
EXPECT_EQ(3, samples[0]);
|
||||
EXPECT_EQ(5, samples[1]);
|
||||
|
||||
provider->GetAudio(samples, 1, 1);
|
||||
EXPECT_EQ(2, samples[0]);
|
||||
EXPECT_EQ(5, samples[1]);
|
||||
|
||||
provider->GetAudio(samples, 0, 1);
|
||||
EXPECT_EQ(1, samples[0]);
|
||||
EXPECT_EQ(5, samples[1]);
|
||||
|
||||
agi::fs::Remove(path);
|
||||
}
|
||||
|
||||
#define WAVE64_FILE \
|
||||
"riff\x2e\x91\xcf\x11\xa5\xd6\x28\xdb\x04\xc1\x00\x00" /* RIFF GUID */ \
|
||||
"\x74\x00\0\0\0\0\0\0" /* file size */ \
|
||||
"wave\xf3\xac\xd3\x11\x8c\xd1\x00\xc0\x4f\x8e\xdb\x8a" /* WAVE GUID */ \
|
||||
"fmt \xf3\xac\xd3\x11\x8c\xd1\x00\xc0\x4f\x8e\xdb\x8a" /* fmt GUID */ \
|
||||
"\x30\x00\0\0\0\0\0\0" /* fmt chunk size */ \
|
||||
"\1\0\1\0\x10\0\0\0\x20\0\0\0\2\0\x10\0\0\0\0\0\0\0\0\0" /* fmt chunk */ \
|
||||
"data\xf3\xac\xd3\x11\x8c\xd1\x00\xc0\x4f\x8e\xdb\x8a" /* data GUID */ \
|
||||
"\x1c\0\0\0\0\0\0\0" /* data chunk size */ \
|
||||
"\1\0\2\0" /* actual sample data */ \
|
||||
|
||||
TEST(lagi_audio, wave64_simple) {
|
||||
auto path = agi::Path().Decode("?temp/w64_valid");
|
||||
WRITE(WAVE64_FILE);
|
||||
|
||||
auto provider = agi::CreatePCMAudioProvider(path, nullptr);
|
||||
ASSERT_EQ(2, provider->GetNumSamples());
|
||||
|
||||
uint16_t samples[2];
|
||||
provider->GetAudio(samples, 0, 2);
|
||||
EXPECT_EQ(1, samples[0]);
|
||||
EXPECT_EQ(2, samples[1]);
|
||||
|
||||
agi::fs::Remove(path);
|
||||
}
|
||||
|
||||
TEST(lagi_audio, wave64_truncated) {
|
||||
auto path = agi::Path().Decode("?temp/w64_truncated");
|
||||
|
||||
// Should be invalid until there's an entire sample
|
||||
for (size_t i = 0; i < sizeof(WAVE64_FILE) - 4; ++i) {
|
||||
bfs::ofstream s(path);
|
||||
s.write(WAVE64_FILE, i);
|
||||
ASSERT_THROW(agi::CreatePCMAudioProvider(path, nullptr), agi::AudioDataNotFound);
|
||||
}
|
||||
|
||||
{
|
||||
bfs::ofstream s(path);
|
||||
s.write(WAVE64_FILE, sizeof(WAVE64_FILE) - 3);
|
||||
}
|
||||
ASSERT_NO_THROW(agi::CreatePCMAudioProvider(path, nullptr));
|
||||
|
||||
{
|
||||
auto provider = agi::CreatePCMAudioProvider(path, nullptr);
|
||||
uint16_t sample;
|
||||
provider->GetAudio(&sample, 0, 1);
|
||||
EXPECT_EQ(1, sample);
|
||||
}
|
||||
|
||||
agi::fs::Remove(path);
|
||||
}
|
|
@ -135,7 +135,7 @@ TEST(lagi_iconv, wchar_tSupport) {
|
|||
}
|
||||
|
||||
TEST(lagi_iconv, Roundtrip) {
|
||||
for (auto const& name : GetEncodingsList<std::vector<std::string>>()) {
|
||||
for (auto const& name : GetEncodingsList<std::vector<std::string>>()) {
|
||||
ASSERT_NO_THROW(IconvWrapper("utf-8", name.c_str()));
|
||||
ASSERT_NO_THROW(IconvWrapper(name.c_str(), "utf-8"));
|
||||
EXPECT_EQ(
|
||||
|
|
Loading…
Reference in a new issue