From 585e9489d97a0529683a495a78a679075b9979dc Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Wed, 9 Jul 2014 07:22:49 -0700 Subject: [PATCH] Move some of the audio provider machinery to libaegisub And add tests. --- Makefile.target | 2 +- libaegisub/Makefile | 1 + libaegisub/audio/provider.cpp | 137 +++++++ .../audio/provider_convert.cpp | 51 +-- libaegisub/audio/provider_dummy.cpp | 80 ++++ .../audio/provider_hd.cpp | 46 +-- .../audio/provider_lock.cpp | 15 +- libaegisub/audio/provider_pcm.cpp | 239 +++++++++++ .../audio/provider_ram.cpp | 53 +-- .../include/libaegisub/audio/provider.h | 95 +++++ src/Makefile | 10 +- src/audio_controller.cpp | 6 +- src/audio_controller.h | 34 +- src/audio_display.cpp | 4 +- src/audio_display.h | 6 +- src/audio_karaoke.cpp | 2 +- src/audio_karaoke.h | 4 +- src/audio_player.cpp | 29 +- src/audio_player_alsa.cpp | 10 +- src/audio_player_dsound.cpp | 14 +- src/audio_player_dsound2.cpp | 22 +- src/audio_player_openal.cpp | 20 +- src/audio_player_oss.cpp | 17 +- src/audio_player_portaudio.cpp | 16 +- src/audio_player_pulse.cpp | 20 +- src/audio_provider.cpp | 191 --------- src/audio_provider_avs.cpp | 4 +- src/audio_provider_dummy.cpp | 88 ---- src/audio_provider_factory.cpp | 126 ++++++ src/audio_provider_factory.h | 27 ++ src/audio_provider_ffmpegsource.cpp | 23 +- src/audio_provider_pcm.cpp | 375 ------------------ src/audio_renderer.cpp | 7 +- src/audio_renderer.h | 12 +- src/audio_renderer_spectrum.cpp | 2 +- src/audio_renderer_waveform.cpp | 3 +- src/command/audio.cpp | 59 +-- src/frame_main.cpp | 2 +- src/frame_main.h | 4 +- src/include/aegisub/audio_player.h | 12 +- src/include/aegisub/audio_provider.h | 99 ----- src/preferences.cpp | 4 +- src/project.cpp | 9 +- src/project.h | 8 +- tests/tests/audio.cpp | 356 +++++++++++++++++ tests/tests/iconv.cpp | 2 +- 46 files changed, 1272 insertions(+), 1074 deletions(-) create mode 100644 libaegisub/audio/provider.cpp rename src/audio_provider_convert.cpp => libaegisub/audio/provider_convert.cpp (83%) create mode 100644 libaegisub/audio/provider_dummy.cpp rename src/audio_provider_hd.cpp => libaegisub/audio/provider_hd.cpp (66%) rename src/audio_provider_lock.cpp => libaegisub/audio/provider_lock.cpp (79%) create mode 100644 libaegisub/audio/provider_pcm.cpp rename src/audio_provider_ram.cpp => libaegisub/audio/provider_ram.cpp (51%) create mode 100644 libaegisub/include/libaegisub/audio/provider.h delete mode 100644 src/audio_provider.cpp delete mode 100644 src/audio_provider_dummy.cpp create mode 100644 src/audio_provider_factory.cpp create mode 100644 src/audio_provider_factory.h delete mode 100644 src/audio_provider_pcm.cpp delete mode 100644 src/include/aegisub/audio_provider.h create mode 100644 tests/tests/audio.cpp diff --git a/Makefile.target b/Makefile.target index 5ca482f81..d3b6e1500 100644 --- a/Makefile.target +++ b/Makefile.target @@ -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 diff --git a/libaegisub/Makefile b/libaegisub/Makefile index 1492a6f79..64204ef91 100644 --- a/libaegisub/Makefile +++ b/libaegisub/Makefile @@ -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)) \ diff --git a/libaegisub/audio/provider.cpp b/libaegisub/audio/provider.cpp new file mode 100644 index 000000000..3c027470b --- /dev/null +++ b/libaegisub/audio/provider.cpp @@ -0,0 +1,137 @@ +// Copyright (c) 2014, Thomas Goyne +// +// 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(buf); + for (size_t i = 0; i < (size_t)count; ++i) + buffer[i] = util::mid(-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(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(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 + void write(const char(&str)[N]) { + out.write(str, N - 1); + } + + void write(std::vector const& data) { + out.write(data.data(), data.size()); + } + + template + void write(Src v) { + auto converted = static_cast(v); + out.write(reinterpret_cast(&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(bufsize + 36); + + out.write("WAVEfmt "); + out.write(16); // Size of chunk + out.write(1); // compression format (PCM) + out.write(provider->GetChannels()); + out.write(provider->GetSampleRate()); + out.write(provider->GetSampleRate() * provider->GetChannels() * provider->GetBytesPerSample()); + out.write(provider->GetChannels() * provider->GetBytesPerSample()); + out.write(provider->GetBytesPerSample() * 8); + + out.write("data"); + out.write(bufsize); + + // samples per read + size_t spr = 65536 / bytes_per_sample; + std::vector buf; + for (int64_t i = start_sample; i < end_sample; i += spr) { + spr = std::min(spr, end_sample - i); + buf.resize(spr * bytes_per_sample); + provider->GetAudio(&buf[0], i, spr); + out.write(buf); + } +} +} diff --git a/src/audio_provider_convert.cpp b/libaegisub/audio/provider_convert.cpp similarity index 83% rename from src/audio_provider_convert.cpp rename to libaegisub/audio/provider_convert.cpp index ac4c1af58..a9f6272e8 100644 --- a/src/audio_provider_convert.cpp +++ b/libaegisub/audio/provider_convert.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2011, Thomas Goyne +// Copyright (c) 2014, Thomas Goyne // // 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 #include #include +using namespace agi; + /// Anything integral -> 16 bit signed machine-endian audio converter +namespace { template class BitdepthConvertAudioProvider final : public AudioProviderWrapper { int src_bytes_per_sample; public: BitdepthConvertAudioProvider(std::unique_ptr 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 src_buf(count * src_bytes_per_sample * channels); - source->GetAudio(&src_buf[0], start, count); + std::vector src_buf(count * src_bytes_per_sample * channels); + source->GetAudio(src_buf.data(), start, count); int16_t *dest = reinterpret_cast(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(*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(src_bytes_per_sample) > sizeof(Target)) sample >>= (src_bytes_per_sample - sizeof(Target)) * 8; else if (static_cast(src_bytes_per_sample) < sizeof(Target)) sample <<= (sizeof(Target) - src_bytes_per_sample ) * 8; - dest[i] = (Target)sample; + dest[i] = static_cast(sample); } } }; @@ -82,7 +80,7 @@ public: std::vector src_buf(count * channels); source->GetAudio(&src_buf[0], start, count); - Target *dest = reinterpret_cast(buf); + auto dest = reinterpret_cast(buf); for (size_t i = 0; i < static_cast(count * channels); ++i) { Source expanded; @@ -107,9 +105,9 @@ class DownmixAudioProvider final : public AudioProviderWrapper { public: DownmixAudioProvider(std::unique_ptr 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 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(buf); + auto buf16 = reinterpret_cast(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 CreateConvertAudioProvider(std::unique_ptr provider) { // Ensure 16-bit audio with proper endianness if (provider->AreSamplesFloat()) { @@ -200,3 +200,4 @@ std::unique_ptr CreateConvertAudioProvider(std::unique_ptr +// +// 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 +#include + +/* + * 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 uniform_dist(-5000, 5000); + for (size_t i = 0; i < count; ++i) + static_cast(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 CreateDummyAudioProvider(agi::fs::path const& file, agi::BackgroundRunner *) { + if (!boost::starts_with(file.string(), "dummy-audio:")) + return {}; + return agi::make_unique(file); +} +} diff --git a/src/audio_provider_hd.cpp b/libaegisub/audio/provider_hd.cpp similarity index 66% rename from src/audio_provider_hd.cpp rename to libaegisub/audio/provider_hd.cpp index d22c81be9..4dd6234c7 100644 --- a/src/audio_provider_hd.cpp +++ b/libaegisub/audio/provider_hd.cpp @@ -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 #include @@ -27,11 +24,14 @@ #include #include +#include #include namespace { +using namespace agi; + class HDAudioProvider final : public AudioProviderWrapper { - std::unique_ptr file; + mutable temp_file_mapping file; std::atomic 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 src) + HDAudioProvider(std::unique_ptr 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(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 CreateHDAudioProvider(std::unique_ptr src) { - return agi::make_unique(std::move(src)); +namespace agi { +std::unique_ptr CreateHDAudioProvider(std::unique_ptr src, agi::fs::path const& dir) { + return make_unique(std::move(src), dir); +} } diff --git a/src/audio_provider_lock.cpp b/libaegisub/audio/provider_lock.cpp similarity index 79% rename from src/audio_provider_lock.cpp rename to libaegisub/audio/provider_lock.cpp index 904832898..eb397e410 100644 --- a/src/audio_provider_lock.cpp +++ b/libaegisub/audio/provider_lock.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2012, Thomas Goyne +// Copyright (c) 2014, Thomas Goyne // // 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 -#include #include 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 CreateLockAudioProvider(std::unique_ptr src) { return agi::make_unique(std::move(src)); } +} diff --git a/libaegisub/audio/provider_pcm.cpp b/libaegisub/audio/provider_pcm.cpp new file mode 100644 index 000000000..78efbfe89 --- /dev/null +++ b/libaegisub/audio/provider_pcm.cpp @@ -0,0 +1,239 @@ +// Copyright (c) 2014, Thomas Goyne +// +// 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 + +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(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(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 + 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(data); + } + + std::vector index_points; +}; + +struct FourCC { + std::array 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: +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 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 +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::max(); + if (*Read(&data_left) != Impl::riff_id()) + throw AudioDataNotFound("File is not a RIFF file"); + + data_left = Impl::data_size(*Read(&data_left)); + + if (*Read(&data_left) != Impl::wave_id()) + throw AudioDataNotFound("File is not a RIFF WAV file"); + + while (data_left) { + auto chunk_fcc = *Read(&data_left); + auto chunk_size = Impl::chunk_size(*Read(&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(&chunk_size); + if (compression != 1) + throw AudioProviderError("File is not uncompressed PCM"); + + channels = *Read(&chunk_size); + sample_rate = *Read(&chunk_size); + Read(&chunk_size); // Average bytes per sample; meaningless + Read(&chunk_size); // Block align + bytes_per_sample = (*Read(&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 CreatePCMAudioProvider(fs::path const& filename, BackgroundRunner *) { + bool wrong_file_type = true; + std::string msg; + + try { + return make_unique>(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>(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); +} +} diff --git a/src/audio_provider_ram.cpp b/libaegisub/audio/provider_ram.cpp similarity index 51% rename from src/audio_provider_ram.cpp rename to libaegisub/audio/provider_ram.cpp index 1a3a4e174..f177840b5 100644 --- a/src/audio_provider_ram.cpp +++ b/libaegisub/audio/provider_ram.cpp @@ -1,48 +1,29 @@ -// Copyright (c) 2005-2006, Rodrigo Braz Monteiro, Fredrik Mellbin -// All rights reserved. +// Copyright (c) 2014, Thomas Goyne // -// 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 +#include "libaegisub/make_unique.h" #include #include #include 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 CreateRAMAudioProvider(std::unique_ptr src) { - return agi::make_unique(std::move(src)); + return make_unique(std::move(src)); +} } diff --git a/libaegisub/include/libaegisub/audio/provider.h b/libaegisub/include/libaegisub/audio/provider.h new file mode 100644 index 000000000..9e631bc40 --- /dev/null +++ b/libaegisub/include/libaegisub/audio/provider.h @@ -0,0 +1,95 @@ +// Copyright (c) 2014, Thomas Goyne +// +// 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 +#include + +#include +#include + +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 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 source; +public: + AudioProviderWrapper(std::unique_ptr 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 CreateDummyAudioProvider(fs::path const& filename, BackgroundRunner *); +std::unique_ptr CreatePCMAudioProvider(fs::path const& filename, BackgroundRunner *); + +std::unique_ptr CreateConvertAudioProvider(std::unique_ptr source_provider); +std::unique_ptr CreateLockAudioProvider(std::unique_ptr source_provider); +std::unique_ptr CreateHDAudioProvider(std::unique_ptr source_provider, fs::path const& dir); +std::unique_ptr CreateRAMAudioProvider(std::unique_ptr source_provider); + +void SaveAudioClip(AudioProvider *provider, fs::path const& path, int start_time, int end_time); +} diff --git a/src/Makefile b/src/Makefile index 76bf2127d..60d71015f 100644 --- a/src/Makefile +++ b/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) diff --git a/src/audio_controller.cpp b/src/audio_controller.cpp index af7964531..4de1928d1 100644 --- a/src/audio_controller.cpp +++ b/src/audio_controller.cpp @@ -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 + #include 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(); diff --git a/src/audio_controller.h b/src/audio_controller.h index 2f4c1bbed..6bc192762 100644 --- a/src/audio_controller.h +++ b/src/audio_controller.h @@ -36,10 +36,10 @@ #include 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); -} diff --git a/src/audio_display.cpp b/src/audio_display.cpp index dfb6cb9ad..d8c6fb56c 100644 --- a/src/audio_display.cpp +++ b/src/audio_display.cpp @@ -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 +#include #include #include @@ -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; diff --git a/src/audio_display.h b/src/audio_display.h index faaf403a9..db607bcdd 100644 --- a/src/audio_display.h +++ b/src/audio_display.h @@ -40,12 +40,12 @@ #include #include +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 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(); diff --git a/src/audio_karaoke.cpp b/src/audio_karaoke.cpp index 4f42bc970..60dd4c390 100644 --- a/src/audio_karaoke.cpp +++ b/src/audio_karaoke.cpp @@ -120,7 +120,7 @@ void AudioKaraoke::OnFileChanged(int type, std::set const& } } -void AudioKaraoke::OnAudioOpened(AudioProvider *provider) { +void AudioKaraoke::OnAudioOpened(agi::AudioProvider *provider) { if (provider) SetEnabled(enabled); else diff --git a/src/audio_karaoke.h b/src/audio_karaoke.h index 0cc7c3b39..e798bfc2b 100644 --- a/src/audio_karaoke.h +++ b/src/audio_karaoke.h @@ -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: diff --git a/src/audio_player.cpp b/src/audio_player.cpp index c4500bdff..f5a8327ca 100644 --- a/src/audio_player.cpp +++ b/src/audio_player.cpp @@ -40,23 +40,18 @@ #include -AudioPlayer::AudioPlayer(AudioProvider *provider) -: provider(provider) -{ -} - -std::unique_ptr CreateAlsaPlayer(AudioProvider *providers, wxWindow *window); -std::unique_ptr CreateDirectSoundPlayer(AudioProvider *providers, wxWindow *window); -std::unique_ptr CreateDirectSound2Player(AudioProvider *providers, wxWindow *window); -std::unique_ptr CreateOpenALPlayer(AudioProvider *providers, wxWindow *window); -std::unique_ptr CreatePortAudioPlayer(AudioProvider *providers, wxWindow *window); -std::unique_ptr CreatePulseAudioPlayer(AudioProvider *providers, wxWindow *window); -std::unique_ptr CreateOSSPlayer(AudioProvider *providers, wxWindow *window); +std::unique_ptr CreateAlsaPlayer(agi::AudioProvider *providers, wxWindow *window); +std::unique_ptr CreateDirectSoundPlayer(agi::AudioProvider *providers, wxWindow *window); +std::unique_ptr CreateDirectSound2Player(agi::AudioProvider *providers, wxWindow *window); +std::unique_ptr CreateOpenALPlayer(agi::AudioProvider *providers, wxWindow *window); +std::unique_ptr CreatePortAudioPlayer(agi::AudioProvider *providers, wxWindow *window); +std::unique_ptr CreatePulseAudioPlayer(agi::AudioProvider *providers, wxWindow *window); +std::unique_ptr CreateOSSPlayer(agi::AudioProvider *providers, wxWindow *window); namespace { struct factory { const char *name; - std::unique_ptr (*create)(AudioProvider *, wxWindow *window); + std::unique_ptr (*create)(agi::AudioProvider *, wxWindow *window); bool hidden; }; @@ -87,9 +82,9 @@ std::vector AudioPlayerFactory::GetClasses() { return ::GetClasses(boost::make_iterator_range(std::begin(factories), std::end(factories))); } -std::unique_ptr AudioPlayerFactory::GetAudioPlayer(AudioProvider *provider, wxWindow *window) { +std::unique_ptr 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 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); } diff --git a/src/audio_player_alsa.cpp b/src/audio_player_alsa.cpp index fcd171242..5a1705622 100644 --- a/src/audio_player_alsa.cpp +++ b/src/audio_player_alsa.cpp @@ -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 #include #include @@ -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 CreateAlsaPlayer(AudioProvider *provider, wxWindow *) +std::unique_ptr CreateAlsaPlayer(agi::AudioProvider *provider, wxWindow *) { return agi::make_unique(provider); } diff --git a/src/audio_player_dsound.cpp b/src/audio_player_dsound.cpp index 06ed2ca5d..01b47b354 100644 --- a/src/audio_player_dsound.cpp +++ b/src/audio_player_dsound.cpp @@ -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 #include #include @@ -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 CreateDirectSoundPlayer(AudioProvider *provider, wxWindow *parent) { +std::unique_ptr CreateDirectSoundPlayer(agi::AudioProvider *provider, wxWindow *parent) { return agi::make_unique(provider, parent); } diff --git a/src/audio_player_dsound2.cpp b/src/audio_player_dsound2.cpp index 1e8081dba..1f937d34b 100644 --- a/src/audio_player_dsound2.cpp +++ b/src/audio_player_dsound2.cpp @@ -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 #include #include #include @@ -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 CreateDirectSound2Player(AudioProvider *provider, wxWindow *parent) { +std::unique_ptr CreateDirectSound2Player(agi::AudioProvider *provider, wxWindow *parent) { return agi::make_unique(provider, parent); } diff --git a/src/audio_player_openal.cpp b/src/audio_player_openal.cpp index 0b3b1ee7f..302878e25 100644 --- a/src/audio_player_openal.cpp +++ b/src/audio_player_openal.cpp @@ -36,9 +36,9 @@ #include "include/aegisub/audio_player.h" #include "audio_controller.h" -#include "include/aegisub/audio_provider.h" #include "utils.h" +#include #include #include @@ -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 CreateOpenALPlayer(AudioProvider *provider, wxWindow *) +std::unique_ptr CreateOpenALPlayer(agi::AudioProvider *provider, wxWindow *) { return agi::make_unique(provider); } diff --git a/src/audio_player_oss.cpp b/src/audio_player_oss.cpp index 3b7548c24..fb52b87fc 100644 --- a/src/audio_player_oss.cpp +++ b/src/audio_player_oss.cpp @@ -35,10 +35,10 @@ #include "audio_controller.h" #include "compat.h" -#include "include/aegisub/audio_provider.h" #include "options.h" #include "utils.h" +#include #include #include @@ -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 CreateOSSPlayer(AudioProvider *provider, wxWindow *) { +std::unique_ptr CreateOSSPlayer(agi::AudioProvider *provider, wxWindow *) { return agi::make_unique(provider); } diff --git a/src/audio_player_portaudio.cpp b/src/audio_player_portaudio.cpp index 37e884399..7a5babcdc 100644 --- a/src/audio_player_portaudio.cpp +++ b/src/audio_player_portaudio.cpp @@ -37,15 +37,13 @@ #include "audio_controller.h" #include "compat.h" -#include "include/aegisub/audio_provider.h" #include "options.h" #include "utils.h" +#include #include #include -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 CreatePortAudioPlayer(AudioProvider *provider, wxWindow *) { +std::unique_ptr CreatePortAudioPlayer(agi::AudioProvider *provider, wxWindow *) { return agi::make_unique(provider); } diff --git a/src/audio_player_pulse.cpp b/src/audio_player_pulse.cpp index 1c2ce2d9c..7174356bd 100644 --- a/src/audio_player_pulse.cpp +++ b/src/audio_player_pulse.cpp @@ -36,9 +36,9 @@ #include "include/aegisub/audio_player.h" #include "audio_controller.h" -#include "include/aegisub/audio_provider.h" #include "utils.h" +#include #include #include @@ -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 CreatePulseAudioPlayer(AudioProvider *provider, wxWindow *) { +std::unique_ptr CreatePulseAudioPlayer(agi::AudioProvider *provider, wxWindow *) { return agi::make_unique(provider); } #endif // WITH_LIBPULSE diff --git a/src/audio_provider.cpp b/src/audio_provider.cpp deleted file mode 100644 index 58387e539..000000000 --- a/src/audio_provider.cpp +++ /dev/null @@ -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 -#include - -#include - -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(buf); - for (size_t i = 0; i < (size_t)count; ++i) - buffer[i] = mid(-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(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(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 CreateDummyAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *); -std::unique_ptr CreatePCMAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *); -std::unique_ptr CreateAvisynthAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *); -std::unique_ptr CreateFFmpegSourceAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *); - -std::unique_ptr CreateConvertAudioProvider(std::unique_ptr source_provider); -std::unique_ptr CreateLockAudioProvider(std::unique_ptr source_provider); -std::unique_ptr CreateHDAudioProvider(std::unique_ptr source_provider); -std::unique_ptr CreateRAMAudioProvider(std::unique_ptr source_provider); - -namespace { - struct factory { - const char *name; - std::unique_ptr (*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 AudioProviderFactory::GetClasses() { - return ::GetClasses(boost::make_iterator_range(std::begin(providers), std::end(providers))); -} - -std::unique_ptr 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 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"); -} diff --git a/src/audio_provider_avs.cpp b/src/audio_provider_avs.cpp index 24be20e10..26480188b 100644 --- a/src/audio_provider_avs.cpp +++ b/src/audio_provider_avs.cpp @@ -33,7 +33,7 @@ /// #ifdef WITH_AVISYNTH -#include "include/aegisub/audio_provider.h" +#include #include "avisynth.h" #include "avisynth_wrap.h" @@ -50,7 +50,7 @@ #include namespace { -class AvisynthAudioProvider final : public AudioProvider { +class AvisynthAudioProvider final : public agi::AudioProvider { AviSynthWrapper avs_wrapper; PClip clip; diff --git a/src/audio_provider_dummy.cpp b/src/audio_provider_dummy.cpp deleted file mode 100644 index 6a1dc9d08..000000000 --- a/src/audio_provider_dummy.cpp +++ /dev/null @@ -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 - -#include -#include - -/* - * 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(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 CreateDummyAudioProvider(agi::fs::path const& file, agi::BackgroundRunner *) { - if (!boost::starts_with(file.string(), "dummy-audio:")) - return {}; - return agi::make_unique(file); -} diff --git a/src/audio_provider_factory.cpp b/src/audio_provider_factory.cpp new file mode 100644 index 000000000..0e5fe3589 --- /dev/null +++ b/src/audio_provider_factory.cpp @@ -0,0 +1,126 @@ +// Copyright (c) 2014, Thomas Goyne +// +// 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 +#include +#include +#include + +#include + +using namespace agi; + +std::unique_ptr CreateAvisynthAudioProvider(fs::path const& filename, BackgroundRunner *); +std::unique_ptr CreateFFmpegSourceAudioProvider(fs::path const& filename, BackgroundRunner *); + +namespace { +struct factory { + const char *name; + std::unique_ptr (*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 GetAudioProviderNames() { + return ::GetClasses(boost::make_iterator_range(std::begin(providers), std::end(providers))); +} + +std::unique_ptr 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 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"); +} diff --git a/src/audio_provider_factory.h b/src/audio_provider_factory.h new file mode 100644 index 000000000..bbd7af972 --- /dev/null +++ b/src/audio_provider_factory.h @@ -0,0 +1,27 @@ +// Copyright (c) 2014, Thomas Goyne +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ + +#include + +#include + +namespace agi { + class AudioProvider; + class BackgroundRunner; +} + +std::unique_ptr GetAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *br); +std::vector GetAudioProviderNames(); diff --git a/src/audio_provider_ffmpegsource.cpp b/src/audio_provider_ffmpegsource.cpp index 3a89f6506..4e44b1d29 100644 --- a/src/audio_provider_ffmpegsource.cpp +++ b/src/audio_provider_ffmpegsource.cpp @@ -33,9 +33,8 @@ /// #ifdef WITH_FFMS2 -#include "include/aegisub/audio_provider.h" +#include -#include "audio_controller.h" #include "ffmpegsource_common.h" #include "options.h" @@ -45,7 +44,7 @@ #include namespace { -class FFmpegSourceAudioProvider final : public AudioProvider, FFmpegSourceProvider { +class FFmpegSourceAudioProvider final : public agi::AudioProvider, FFmpegSourceProvider { /// audio source object agi::scoped_holder 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 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 CreateFFmpegSourceAudioProvider(agi::fs::path const& file, agi::BackgroundRunner *br) { +std::unique_ptr CreateFFmpegSourceAudioProvider(agi::fs::path const& file, agi::BackgroundRunner *br) { return agi::make_unique(file, br); } diff --git a/src/audio_provider_pcm.cpp b/src/audio_provider_pcm.cpp deleted file mode 100644 index 945dc007b..000000000 --- a/src/audio_provider_pcm.cpp +++ /dev/null @@ -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 -#include -#include - -#include - -class PCMAudioProvider : public AudioProvider { - void FillBuffer(void *buf, int64_t start, int64_t count) const override; - -protected: - std::unique_ptr file; - - PCMAudioProvider(agi::fs::path const& filename) - : file(agi::make_unique(filename)) - { - float_samples = false; - } - - const char *EnsureRangeAccessible(int64_t start, int64_t length) const { - try { - return file->read(start, static_cast(length)); - } - catch (agi::fs::FileSystemError const& e) { - throw AudioDecodeError(e.GetMessage()); - } - } - - template - T const& Read(int64_t start) const { - return *reinterpret_cast(EnsureRangeAccessible(start, sizeof(T))); - } - - struct IndexPoint { - int64_t start_byte; - int64_t num_samples; - }; - std::vector index_points; -}; - -void PCMAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const { - auto write_buf = static_cast(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: -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(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(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(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(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(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(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(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(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(filepos), - static_cast(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 CreatePCMAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *) { - bool wrong_file_type = true; - std::string msg; - - try { - return agi::make_unique(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(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); -} diff --git a/src/audio_renderer.cpp b/src/audio_renderer.cpp index 90872fce2..3ae563af0 100644 --- a/src/audio_renderer.cpp +++ b/src/audio_renderer.cpp @@ -33,8 +33,7 @@ #include "audio_renderer.h" -#include "include/aegisub/audio_provider.h" - +#include #include #include @@ -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(); diff --git a/src/audio_renderer.h b/src/audio_renderer.h index c0f53ee97..7bfeb2428 100644 --- a/src/audio_renderer.h +++ b/src/audio_renderer.h @@ -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 diff --git a/src/audio_renderer_spectrum.cpp b/src/audio_renderer_spectrum.cpp index 5aedb4b10..8f8eaee90 100644 --- a/src/audio_renderer_spectrum.cpp +++ b/src/audio_renderer_spectrum.cpp @@ -38,8 +38,8 @@ #ifndef WITH_FFTW3 #include "fft.h" #endif -#include "include/aegisub/audio_provider.h" +#include #include #include diff --git a/src/audio_renderer_waveform.cpp b/src/audio_renderer_waveform.cpp index 5d848b1d8..d5bb802fb 100644 --- a/src/audio_renderer_waveform.cpp +++ b/src/audio_renderer_waveform.cpp @@ -30,9 +30,10 @@ #include "audio_renderer_waveform.h" #include "audio_colorscheme.h" -#include "include/aegisub/audio_provider.h" #include "options.h" +#include + #include #include diff --git a/src/command/audio.cpp b/src/command/audio.cpp index af219c5fd..b03a24599 100644 --- a/src/command/audio.cpp +++ b/src/command/audio.cpp @@ -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 #include #include @@ -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 - void write(const char(&str)[N]) { - out.write(str, N - 1); - } - - void write(std::vector const& data) { - out.write(data.data(), data.size()); - } - - template - void write(Src v) { - auto converted = static_cast(v); - out.write(reinterpret_cast(&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(bufsize + 36); - - out.write("WAVEfmt "); - out.write(16); // Size of chunk - out.write(1); // compression format (PCM) - out.write(provider->GetChannels()); - out.write(provider->GetSampleRate()); - out.write(provider->GetSampleRate() * provider->GetChannels() * provider->GetBytesPerSample()); - out.write(provider->GetChannels() * provider->GetBytesPerSample()); - out.write(provider->GetBytesPerSample() * 8); - - out.write("data"); - out.write(bufsize); - - // samples per read - size_t spr = 65536 / bytes_per_sample; - std::vector buf(bufsize); - for (int64_t i = start_sample; i < end_sample; i += spr) { - buf.resize(std::min(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); } }; diff --git a/src/frame_main.cpp b/src/frame_main.cpp index 3d749900b..ba107d6c1 100644 --- a/src/frame_main.cpp +++ b/src/frame_main.cpp @@ -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 diff --git a/src/frame_main.h b/src/frame_main.h index b91d3b2a6..e03295aed 100644 --- a/src/frame_main.h +++ b/src/frame_main.h @@ -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(); diff --git a/src/include/aegisub/audio_player.h b/src/include/aegisub/audio_player.h index 5293e13cd..8e29e1628 100644 --- a/src/include/aegisub/audio_player.h +++ b/src/include/aegisub/audio_player.h @@ -34,20 +34,22 @@ #pragma once +#include + #include #include #include #include -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 GetClasses(); - static std::unique_ptr GetAudioPlayer(AudioProvider *provider, wxWindow *window); + static std::unique_ptr GetAudioPlayer(agi::AudioProvider *provider, wxWindow *window); }; + +DEFINE_EXCEPTION(AudioPlayerOpenError, agi::Exception); diff --git a/src/include/aegisub/audio_provider.h b/src/include/aegisub/audio_provider.h deleted file mode 100644 index 5ea04c6ea..000000000 --- a/src/include/aegisub/audio_provider.h +++ /dev/null @@ -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 -#include - -#include -#include - -class AudioProvider { -protected: - int channels; - - /// for one channel, ie. number of PCM frames - int64_t num_samples; - std::atomic 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 source; -public: - AudioProviderWrapper(std::unique_ptr 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 GetClasses(); - - /// Get a provider for the file - /// @param filename URI to open - static std::unique_ptr 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); diff --git a/src/preferences.cpp b/src/preferences.cpp index aa66b5f19..e1c2b190c 100644 --- a/src/preferences.cpp +++ b/src/preferences.cpp @@ -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()); diff --git a/src/project.cpp b/src/project.cpp index 90282ccfd..21bfe23ce 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -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 #include #include #include @@ -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) { diff --git a/src/project.h b/src/project.h index b51cf4dc9..1fd034d08 100644 --- a/src/project.h +++ b/src/project.h @@ -23,14 +23,14 @@ #include 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 audio_provider; std::unique_ptr video_provider; agi::vfr::Framerate timecodes; std::vector 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 AnnounceAudioProviderModified; agi::signal::Signal AnnounceVideoProviderModified; agi::signal::Signal AnnounceTimecodesModified; agi::signal::Signal 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); diff --git a/tests/tests/audio.cpp b/tests/tests/audio.cpp new file mode 100644 index 000000000..b30d69ffb --- /dev/null +++ b/tests/tests/audio.cpp @@ -0,0 +1,356 @@ +// Copyright (c) 2014, Thomas Goyne +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ + +#include + +#include +#include +#include +#include +#include + +#include + +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(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()); + 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((1 << 22) - 256 + i), buff[i]); +} + +TEST(lagi_audio, hd_cache) { + auto provider = agi::CreateHDAudioProvider(agi::make_unique(), 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((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); +} diff --git a/tests/tests/iconv.cpp b/tests/tests/iconv.cpp index 6d7197cb1..cfec40f7b 100644 --- a/tests/tests/iconv.cpp +++ b/tests/tests/iconv.cpp @@ -135,7 +135,7 @@ TEST(lagi_iconv, wchar_tSupport) { } TEST(lagi_iconv, Roundtrip) { - for (auto const& name : GetEncodingsList>()) { + for (auto const& name : GetEncodingsList>()) { ASSERT_NO_THROW(IconvWrapper("utf-8", name.c_str())); ASSERT_NO_THROW(IconvWrapper(name.c_str(), "utf-8")); EXPECT_EQ(