1
// Copyright (c) 2007-2008, Niels Martin Hansen
2
// All rights reserved.
4
// Redistribution and use in source and binary forms, with or without
5
// modification, are permitted provided that the following conditions are met:
7
// * Redistributions of source code must retain the above copyright notice,
8
// this list of conditions and the following disclaimer.
9
// * Redistributions in binary form must reproduce the above copyright notice,
10
// this list of conditions and the following disclaimer in the documentation
11
// and/or other materials provided with the distribution.
12
// * Neither the name of the Aegisub Group nor the names of its contributors
13
// may be used to endorse or promote products derived from this software
14
// without specific prior written permission.
16
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26
// POSSIBILITY OF SUCH DAMAGE.
28
// -----------------------------------------------------------------------------
32
// Website: http://aegisub.cellosoft.com
33
// Contact: mailto:jiifurusu@gmail.com
39
#include <wx/filename.h>
41
#include "audio_provider_pcm.h"
43
#include "aegisub_endian.h"
49
#include <sys/fcntl.h>
55
PCMAudioProvider::PCMAudioProvider(const wxString &filename)
59
file_handle = CreateFile(
62
FILE_SHARE_READ|FILE_SHARE_WRITE,
65
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_RANDOM_ACCESS,
68
if (file_handle == INVALID_HANDLE_VALUE) {
69
wxLogWarning(_T("PCM audio provider: Could not open audio file for reading (%d)"), GetLastError());
70
throw _T("PCM audio provider: Could not open audio file for reading");
73
LARGE_INTEGER li_file_size = {0};
74
if (!GetFileSizeEx(file_handle, &li_file_size)) {
75
CloseHandle(file_handle);
76
throw _T("PCM audio provider: Failed getting file size");
78
file_size = li_file_size.QuadPart;
80
file_mapping = CreateFileMapping(
87
if (file_mapping == 0) {
88
CloseHandle(file_handle);
89
throw _T("PCM audio provider: Failed creating file mapping");
96
file_handle = open(filename.mb_str(*wxConvFileName), O_RDONLY);
98
if (file_handle == -1) {
99
throw _T("PCM audio provider: Could not open audio file for reading");
102
struct stat filestats;
103
memset(&filestats, 0, sizeof(filestats));
104
if (fstat(file_handle, &filestats)) {
106
throw _T("PCM audio provider: Could not stat file to get size");
108
file_size = filestats.st_size;
116
PCMAudioProvider::~PCMAudioProvider()
120
if (current_mapping) {
121
UnmapViewOfFile(current_mapping);
124
CloseHandle(file_mapping);
125
CloseHandle(file_handle);
129
if (current_mapping) {
130
munmap(current_mapping, mapping_length);
139
char * PCMAudioProvider::EnsureRangeAccessible(int64_t range_start, int64_t range_length)
141
if (range_start + range_length > file_size) {
142
throw _T("PCM audio provider: Attempted to map beyond end of file");
145
// Check whether the requested range is already visible
146
if (!current_mapping || range_start < mapping_start || range_start+range_length > mapping_start+(int64_t)mapping_length) {
148
// It's not visible, change the current mapping
150
if (current_mapping) {
152
UnmapViewOfFile(current_mapping);
154
munmap(current_mapping, mapping_length);
158
// Align range start on a 1 MB boundary and go 16 MB back
159
mapping_start = (range_start & ~0xFFFFF) - 0x1000000;
160
if (mapping_start < 0) mapping_start = 0;
162
if (sizeof(void*) > 4)
163
// Large address space, use a 2 GB mapping
164
mapping_length = 0x80000000;
166
// Small (32 bit) address space, use a 256 MB mapping
167
mapping_length = 0x10000000;
169
// Make sure to always make a mapping at least as large as the requested range
170
if ((int64_t)mapping_length < range_length) {
171
if (range_length > (int64_t)(~(size_t)0))
172
throw _T("PCM audio provider: Requested range larger than max size_t, cannot create view of file");
174
mapping_length = range_length;
176
// But also make sure we don't try to make a mapping larger than the file
177
if (mapping_start + (int64_t)mapping_length > file_size)
178
mapping_length = (size_t)(file_size - mapping_start);
179
// We already checked that the requested range doesn't extend over the end of the file
180
// Hopefully this should ensure that small files are always mapped in their entirety
183
LARGE_INTEGER mapping_start_li;
184
mapping_start_li.QuadPart = mapping_start;
185
current_mapping = MapViewOfFile(
186
file_mapping, // Mapping handle
187
FILE_MAP_READ, // Access type
188
mapping_start_li.HighPart, // Offset high-part
189
mapping_start_li.LowPart, // Offset low-part
190
mapping_length); // Length of view
192
current_mapping = mmap(0, mapping_length, PROT_READ, MAP_PRIVATE, file_handle, mapping_start);
195
if (!current_mapping) {
196
throw _T("PCM audio provider: Failed mapping a view of the file");
201
assert(current_mapping);
202
assert(range_start >= mapping_start);
204
// Difference between actual current mapping start and requested range start
205
ptrdiff_t rel_ofs = (ptrdiff_t)(range_start - mapping_start);
207
// Calculate a pointer into current mapping for the requested range
208
return ((char*)current_mapping) + rel_ofs;
212
void PCMAudioProvider::GetAudio(void *buf, int64_t start, int64_t count)
214
// Read blocks from the file
216
while (count > 0 && index < index_points.size()) {
217
// Check if this index contains the samples we're looking for
218
IndexPoint &ip = index_points[index];
219
if (ip.start_sample <= start && ip.start_sample+ip.num_samples > start) {
221
// How many samples we can maximum take from this block
222
int64_t samples_can_do = ip.num_samples - start + ip.start_sample;
223
if (samples_can_do > count) samples_can_do = count;
225
// Read as many samples we can
226
char *src = EnsureRangeAccessible(
227
ip.start_byte + (start - ip.start_sample) * bytes_per_sample * channels,
228
samples_can_do * bytes_per_sample * channels);
229
memcpy(buf, src, samples_can_do * bytes_per_sample * channels);
232
buf = (char*)buf + samples_can_do * bytes_per_sample * channels;
233
start += samples_can_do;
234
count -= samples_can_do;
241
// If we exhausted all sample sections zerofill the rest
243
if (bytes_per_sample == 1)
244
// 8 bit formats are usually unsigned with bias 127
245
memset(buf, 127, count*channels);
247
// While everything else is signed
248
memset(buf, 0, count*bytes_per_sample*channels);
253
// RIFF WAV PCM provider
254
// Overview of RIFF WAV: <http://www.sonicspot.com/guide/wavefiles.html>
256
class RiffWavPCMAudioProvider : public PCMAudioProvider {
267
// Skip the chunk header here, it's processed separately
268
uint16_t compression; // compression format used -- 0x0001 = PCM
271
uint32_t avg_bytes_sec; // can't always be trusted
272
uint16_t block_align;
273
uint16_t significant_bits_sample;
274
// Here was supposed to be some more fields but we don't need them
275
// and just skipping by the size of the struct wouldn't be safe
276
// either way, as the fields can depend on the compression.
279
static bool CheckFourcc(const char str1[], const char str2[])
284
(str1[0] == str2[0]) &&
285
(str1[1] == str2[1]) &&
286
(str1[2] == str2[2]) &&
287
(str1[3] == str2[3]);
291
RiffWavPCMAudioProvider(const wxString &_filename)
292
: PCMAudioProvider(_filename)
294
filename = _filename;
297
// This should throw an exception if the mapping fails
298
void *filestart = EnsureRangeAccessible(0, sizeof(RIFFChunk));
300
RIFFChunk &header = *(RIFFChunk*)filestart;
302
// Check magic values
303
if (!CheckFourcc(header.ch.type, "RIFF"))
304
throw _T("RIFF PCM WAV audio provider: File is not a RIFF file");
305
if (!CheckFourcc(header.format, "WAVE"))
306
throw _T("RIFF PCM WAV audio provider: File is not a RIFF WAV file");
308
// Count how much more data we can have in the entire file
309
// The first 4 bytes are already eaten by the header.format field
310
uint32_t data_left = Endian::LittleToMachine(header.ch.size) - 4;
311
// How far into the file we have processed.
312
// Must be incremented by the riff chunk size fields.
313
uint32_t filepos = sizeof(header);
315
bool got_fmt_header = false;
317
// Inherited from AudioProvider
320
// Continue reading chunks until out of data
322
ChunkHeader &ch = *(ChunkHeader*)EnsureRangeAccessible(filepos, sizeof(ChunkHeader));
325
data_left -= sizeof(ch);
326
filepos += sizeof(ch);
328
if (CheckFourcc(ch.type, "fmt ")) {
329
if (got_fmt_header) throw _T("RIFF PCM WAV audio provider: Invalid file, multiple 'fmt ' chunks");
330
got_fmt_header = true;
332
fmtChunk &fmt = *(fmtChunk*)EnsureRangeAccessible(filepos, sizeof(fmtChunk));
334
if (Endian::LittleToMachine(fmt.compression) != 1) throw _T("RIFF PCM WAV audio provider: Can't use file, not PCM encoding");
336
// Set stuff inherited from the AudioProvider class
337
sample_rate = Endian::LittleToMachine(fmt.samplerate);
338
channels = Endian::LittleToMachine(fmt.channels);
339
bytes_per_sample = (Endian::LittleToMachine(fmt.significant_bits_sample) + 7) / 8; // round up to nearest whole byte
342
else if (CheckFourcc(ch.type, "data")) {
343
// This won't pick up 'data' chunks inside 'wavl' chunks
344
// since the 'wavl' chunks wrap those.
346
if (!got_fmt_header) throw _T("RIFF PCM WAV audio provider: Found 'data' chunk before 'fmt ' chunk, file is invalid.");
348
int64_t samples = Endian::LittleToMachine(ch.size) / bytes_per_sample;
349
int64_t frames = samples / channels;
352
ip.start_sample = num_samples;
353
ip.num_samples = frames;
354
ip.start_byte = filepos;
355
index_points.push_back(ip);
357
num_samples += frames;
360
// Support wavl (wave list) chunks too?
363
// Make sure they're word aligned
364
data_left -= (Endian::LittleToMachine(ch.size) + 1) & ~1;
365
filepos += (Endian::LittleToMachine(ch.size) + 1) & ~1;
370
bool AreSamplesNativeEndian()
372
// 8 bit samples don't consider endianness
373
if (bytes_per_sample < 2) return true;
374
// Otherwise test whether we're little endian
375
uint32_t testvalue = 0x008800ff;
376
return testvalue == Endian::LittleToMachine(testvalue);
382
// Sony Wave64 audio provider
383
// Specs obtained at: <http://www.vcs.de/fileadmin/user_upload/MBS/PDF/Whitepaper/Informations_about_Sony_Wave64.pdf>
385
static const uint8_t w64GuidRIFF[16] = {
386
// {66666972-912E-11CF-A5D6-28DB04C10000}
387
0x72, 0x69, 0x66, 0x66, 0x2E, 0x91, 0xCF, 0x11, 0xA5, 0xD6, 0x28, 0xDB, 0x04, 0xC1, 0x00, 0x00
390
static const uint8_t w64GuidWAVE[16] = {
391
// {65766177-ACF3-11D3-8CD1-00C04F8EDB8A}
392
0x77, 0x61, 0x76, 0x65, 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A
395
static const uint8_t w64Guidfmt[16] = {
396
// {20746D66-ACF3-11D3-8CD1-00C04F8EDB8A}
397
0x66, 0x6D, 0x74, 0x20, 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A
400
static const uint8_t w64Guiddata[16] = {
401
// {61746164-ACF3-11D3-8CD1-00C04F8EDB8A}
402
0x64, 0x61, 0x74, 0x61, 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A
405
class Wave64AudioProvider : public PCMAudioProvider {
407
// Here's some copy-paste from the FFmpegSource2 code
409
struct WaveFormatEx {
412
uint32_t nSamplesPerSec;
413
uint32_t nAvgBytesPerSec;
414
uint16_t nBlockAlign;
415
uint16_t wBitsPerSample;
420
uint8_t riff_guid[16];
422
uint8_t format_guid[16];
426
uint8_t chunk_guid[16];
433
uint8_t chunk_guid[16];
437
inline bool CheckGuid(const uint8_t *guid1, const uint8_t *guid2)
439
return memcmp(guid1, guid2, 16) == 0;
443
Wave64AudioProvider(const wxString &_filename)
444
: PCMAudioProvider(_filename)
446
filename = _filename;
448
int64_t smallest_possible_file = sizeof(RiffChunk) + sizeof(FormatChunk) + sizeof(DataChunk);
450
if (file_size < smallest_possible_file)
451
throw _T("Wave64 audio provider: File is too small to be a Wave64 file");
454
// This should throw an exception if the mapping fails
455
void *filestart = EnsureRangeAccessible(0, sizeof(RiffChunk));
457
RiffChunk &header = *(RiffChunk*)filestart;
459
// Check magic values
460
if (!CheckGuid(header.riff_guid, w64GuidRIFF))
461
throw _T("Wave64 audio provider: File is not a Wave64 RIFF file");
462
if (!CheckGuid(header.format_guid, w64GuidWAVE))
463
throw _T("Wave64 audio provider: File is not a Wave64 WAVE file");
465
// Count how much more data we can have in the entire file
466
uint64_t data_left = Endian::LittleToMachine(header.file_size) - sizeof(RiffChunk);
467
// How far into the file we have processed.
468
// Must be incremented by the riff chunk size fields.
469
uint64_t filepos = sizeof(header);
471
bool got_fmt_header = false;
473
// Inherited from AudioProvider
476
// Continue reading chunks until out of data
478
uint8_t *chunk_guid = (uint8_t*)EnsureRangeAccessible(filepos, 16);
479
uint64_t chunk_size = Endian::LittleToMachine(*(uint64_t*)EnsureRangeAccessible(filepos+16, sizeof(uint64_t)));
481
if (CheckGuid(chunk_guid, w64Guidfmt)) {
483
throw _T("Wave64 audio provider: Bad file, found more than one 'fmt' chunk");
485
FormatChunk &fmt = *(FormatChunk*)EnsureRangeAccessible(filepos, sizeof(FormatChunk));
486
got_fmt_header = true;
488
if (Endian::LittleToMachine(fmt.format.wFormatTag) == 3)
489
throw _T("Wave64 audio provider: File is IEEE 32 bit float format which isn't supported. Bug the developers if this matters.");
490
if (Endian::LittleToMachine(fmt.format.wFormatTag) != 1)
491
throw _T("Wave64 audio provider: Can't use file, not PCM encoding");
493
// Set stuff inherited from the AudioProvider class
494
sample_rate = Endian::LittleToMachine(fmt.format.nSamplesPerSec);
495
channels = Endian::LittleToMachine(fmt.format.nChannels);
496
bytes_per_sample = (Endian::LittleToMachine(fmt.format.wBitsPerSample) + 7) / 8; // round up to nearest whole byte
499
else if (CheckGuid(chunk_guid, w64Guiddata)) {
501
throw _T("Wave64 audio provider: Found 'data' chunk before 'fmt ' chunk, file is invalid.");
503
int64_t samples = chunk_size / bytes_per_sample;
504
int64_t frames = samples / channels;
507
ip.start_sample = num_samples;
508
ip.num_samples = frames;
509
ip.start_byte = filepos;
510
index_points.push_back(ip);
512
num_samples += frames;
516
// Make sure they're 64 bit aligned
517
data_left -= (chunk_size + 7) & ~7;
518
filepos += (chunk_size + 7) & ~7;
523
bool AreSamplesNativeEndian()
525
// 8 bit samples don't consider endianness
526
if (bytes_per_sample < 2) return true;
527
// Otherwise test whether we're little endian
528
uint32_t testvalue = 0x008800ff;
529
return testvalue == Endian::LittleToMachine(testvalue);
534
AudioProvider *CreatePCMAudioProvider(const wxString &filename)
536
AudioProvider *provider = 0;
538
// Try Microsoft/IBM RIFF WAV
540
provider = new RiffWavPCMAudioProvider(filename);
541
// don't bother trying with anything else if this works
544
catch (const wxChar *) {
550
provider = new Wave64AudioProvider(filename);
553
catch (const wxChar *) {
557
// no providers could be created