// 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 #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 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); T ret; memcpy(&ret, data, sizeof(T)); return ret; } 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 -= std::min(chunk_size, data_left); 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); } }