forked from mia/Aegisub
Make PCM audio provider access the file memory mapped instead of through regular file access, per bug #686. Tested to work on Windows.
I've also added a POSIX implementation but it's untested and might not even build. The implementation is not actually thread safe, but this shouldn't be a problem in most cases, yet. It should still be fixed at some point. Originally committed to SVN as r2130.
This commit is contained in:
parent
3fb93565f5
commit
08910b5a9d
2 changed files with 195 additions and 21 deletions
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2007, Niels Martin Hansen
|
||||
// Copyright (c) 2007-2008, Niels Martin Hansen
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
|
@ -40,12 +40,160 @@
|
|||
#include "utils.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef _WINDOWS
|
||||
#include <sys/mman.h>
|
||||
#include <sys/fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
PCMAudioProvider::PCMAudioProvider(const wxString &filename)
|
||||
{
|
||||
#ifdef _WINDOWS
|
||||
|
||||
file_handle = CreateFile(
|
||||
filename.c_str(),
|
||||
FILE_READ_DATA,
|
||||
FILE_SHARE_READ|FILE_SHARE_WRITE,
|
||||
0,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_RANDOM_ACCESS,
|
||||
0);
|
||||
|
||||
if (file_handle == INVALID_HANDLE_VALUE) {
|
||||
wxLogWarning(_T("PCM audio provider: Could not open audio file for reading (%d)"), GetLastError());
|
||||
throw _T("PCM audio provider: Could not open audio file for reading");
|
||||
}
|
||||
|
||||
LARGE_INTEGER li_file_size = {0};
|
||||
if (!GetFileSizeEx(file_handle, &li_file_size)) {
|
||||
CloseHandle(file_handle);
|
||||
throw _T("PCM audio provider: Failed getting file size");
|
||||
}
|
||||
file_size = li_file_size.QuadPart;
|
||||
|
||||
file_mapping = CreateFileMapping(
|
||||
file_handle,
|
||||
0,
|
||||
PAGE_READONLY,
|
||||
0, 0,
|
||||
0);
|
||||
|
||||
if (file_mapping == 0) {
|
||||
CloseHandle(file_handle);
|
||||
throw _T("PCM audio provider: Failed creating file mapping");
|
||||
}
|
||||
|
||||
current_mapping = 0;
|
||||
|
||||
#else
|
||||
|
||||
file_handle = open(filename.mb_str(wxConvFilename), O_RDONLY);
|
||||
|
||||
if (file_handle == -1) {
|
||||
throw _T("PCM audio provider: Could not open audio file for reading");
|
||||
}
|
||||
|
||||
struct stat filestats = {0};
|
||||
if (fstat(file_handle, &filestats)) {
|
||||
close(file_handle);
|
||||
throw _T("PCM audio provider: Could not stat file to get size");
|
||||
}
|
||||
file_size = filestats.st_size;
|
||||
|
||||
current_mapping = 0;
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
PCMAudioProvider::~PCMAudioProvider()
|
||||
{
|
||||
#ifdef _WINDOWS
|
||||
|
||||
if (current_mapping) {
|
||||
UnmapViewOfFile(current_mapping);
|
||||
}
|
||||
|
||||
CloseHandle(file_mapping);
|
||||
CloseHandle(file_handle);
|
||||
|
||||
#else
|
||||
|
||||
if (current_mapping) {
|
||||
munmap(current_mapping, mapping_length);
|
||||
}
|
||||
|
||||
close(file_handle);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
char * PCMAudioProvider::EnsureRangeAccessible(int64_t range_start, int64_t range_length)
|
||||
{
|
||||
if (range_start + range_length > file_size) {
|
||||
throw _T("PCM audio provider: Attempted to map beyond end of file");
|
||||
}
|
||||
|
||||
// Check whether the requested range is already visible
|
||||
if (range_start < mapping_start || range_start+range_length > mapping_start+mapping_length) {
|
||||
|
||||
// It's not visible, change the current mapping
|
||||
|
||||
if (current_mapping) {
|
||||
#ifdef _WINDOWS
|
||||
UnmapViewOfFile(current_mapping);
|
||||
#else
|
||||
munmap(current_mapping, mapping_length);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Align range start on a 1 MB boundary and go 16 MB back
|
||||
mapping_start = (range_start & ~0xFFFFF) - 0x1000000;
|
||||
if (mapping_start < 0) mapping_start = 0;
|
||||
#ifdef _WINDOWS
|
||||
# if defined(_M_X64) || defined(_M_IA64)
|
||||
// Make 2 GB mappings by default on 64 bit archs
|
||||
mapping_length = 0x80000000;
|
||||
# else
|
||||
// Make 256 MB mappings by default on x86
|
||||
mapping_length = 0x10000000;
|
||||
# endif
|
||||
#else
|
||||
// 256 MB
|
||||
mapping_length = 0x10000000;
|
||||
#endif
|
||||
if (mapping_length > file_size) mapping_length = (size_t)(file_size - mapping_start);
|
||||
// Hopefully this should ensure that small files are always mapped in their entirety
|
||||
|
||||
#ifdef _WINDOWS
|
||||
current_mapping = MapViewOfFile(
|
||||
file_mapping,
|
||||
FILE_MAP_READ,
|
||||
mapping_start >> 32, mapping_start & 0xFFFFFFFF,
|
||||
mapping_length);
|
||||
#else
|
||||
current_mapping = mmap(0, mapping_length, PROT_READ, MAP_PRIVATE, file_handle, mapping_start);
|
||||
#endif
|
||||
|
||||
if (!current_mapping) {
|
||||
throw _T("PCM audio provider: Failed mapping a view of the file");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
assert(range_start >= mapping_start);
|
||||
|
||||
ptrdiff_t rel_ofs = (ptrdiff_t)(range_start - mapping_start);
|
||||
|
||||
return ((char*)current_mapping) + rel_ofs;
|
||||
}
|
||||
|
||||
|
||||
void PCMAudioProvider::GetAudio(void *buf, int64_t start, int64_t count)
|
||||
{
|
||||
// We'll be seeking in the file so state can become inconsistent
|
||||
wxMutexLocker _fml(filemutex);
|
||||
|
||||
// Read blocks from the file
|
||||
size_t index = 0;
|
||||
while (count > 0 && index < index_points.size()) {
|
||||
|
@ -58,8 +206,10 @@ void PCMAudioProvider::GetAudio(void *buf, int64_t start, int64_t count)
|
|||
if (samples_can_do > count) samples_can_do = count;
|
||||
|
||||
// Read as many samples we can
|
||||
file.Seek(ip.start_byte + (start - ip.start_sample) * bytes_per_sample * channels, wxFromStart);
|
||||
file.Read(buf, samples_can_do * bytes_per_sample * channels);
|
||||
char *src = EnsureRangeAccessible(
|
||||
ip.start_byte + (start - ip.start_sample) * bytes_per_sample * channels,
|
||||
samples_can_do * bytes_per_sample * channels);
|
||||
memcpy(buf, src, samples_can_do * bytes_per_sample * channels);
|
||||
|
||||
// Update data
|
||||
buf = (char*)buf + samples_can_do * bytes_per_sample * channels;
|
||||
|
@ -111,15 +261,14 @@ private:
|
|||
|
||||
public:
|
||||
RiffWavPCMAudioProvider(const wxString &_filename)
|
||||
: PCMAudioProvider(_filename)
|
||||
{
|
||||
filename = _filename;
|
||||
|
||||
if (!file.Open(_filename, wxFile::read)) throw _T("RIFF PCM WAV audio provider: Unable to open file for reading");
|
||||
|
||||
// Read header
|
||||
file.Seek(0);
|
||||
RIFFChunk header;
|
||||
if (file.Read(&header, sizeof(header)) < (ssize_t) sizeof(header)) throw _T("RIFF PCM WAV audio provider: file is too small to contain a RIFF header");
|
||||
// Assume we won't get files smaller than 256 bytes
|
||||
void *filestart = EnsureRangeAccessible(0, sizeof(RIFFChunk));
|
||||
RIFFChunk &header = *(RIFFChunk*)filestart;
|
||||
|
||||
// Check that it's good
|
||||
if (strncmp(header.ch.type, "RIFF", 4)) throw _T("RIFF PCM WAV audio provider: File is not a RIFF file");
|
||||
|
@ -139,9 +288,7 @@ public:
|
|||
|
||||
// Continue reading chunks until out of data
|
||||
while (data_left) {
|
||||
file.Seek(filepos);
|
||||
ChunkHeader ch;
|
||||
if (file.Read(&ch, sizeof(ch)) < (ssize_t) sizeof(ch)) break;
|
||||
ChunkHeader &ch = *(ChunkHeader*)EnsureRangeAccessible(filepos, sizeof(ChunkHeader));
|
||||
|
||||
// Update counters
|
||||
data_left -= sizeof(ch);
|
||||
|
@ -151,8 +298,7 @@ public:
|
|||
if (got_fmt_header) throw _T("RIFF PCM WAV audio provider: Invalid file, multiple 'fmt ' chunks");
|
||||
got_fmt_header = true;
|
||||
|
||||
fmtChunk fmt;
|
||||
if (file.Read(&fmt, sizeof(fmt)) < (ssize_t) sizeof(fmt)) throw _T("RIFF PCM WAV audio provider: File ended before end of 'fmt ' chunk");
|
||||
fmtChunk &fmt = *(fmtChunk*)EnsureRangeAccessible(filepos, sizeof(fmtChunk));
|
||||
|
||||
if (fmt.compression != 1) throw _T("RIFF PCM WAV audio provider: Can't use file, not PCM encoding");
|
||||
|
||||
|
@ -279,8 +425,12 @@ AudioProvider *CreatePCMAudioProvider(const wxString &filename)
|
|||
|
||||
// Try Microsoft/IBM RIFF WAV first
|
||||
// XXX: This is going to blow up if built on big endian archs
|
||||
try { provider = new RiffWavPCMAudioProvider(filename); }
|
||||
catch (...) { provider = 0; }
|
||||
try {
|
||||
provider = new RiffWavPCMAudioProvider(filename);
|
||||
}
|
||||
catch (...) {
|
||||
provider = 0;
|
||||
}
|
||||
|
||||
if (provider && provider->GetChannels() > 1) {
|
||||
// Can't feed non-mono audio to the rest of the program.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2007, Niels Martin Hansen
|
||||
// Copyright (c) 2007-2008, Niels Martin Hansen
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
|
@ -44,13 +44,37 @@
|
|||
#include <wx/thread.h>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WINDOWS
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
|
||||
/////////////////////////////
|
||||
// Audio provider base class
|
||||
class PCMAudioProvider : public AudioProvider {
|
||||
private:
|
||||
#ifdef _WINDOWS
|
||||
// File handle and file mapping handle from Win32
|
||||
HANDLE file_handle;
|
||||
HANDLE file_mapping;
|
||||
// Pointer to current area mapped into memory
|
||||
void *current_mapping;
|
||||
// Byte indices in the file that the current mapping covers
|
||||
int64_t mapping_start;
|
||||
size_t mapping_length;
|
||||
#else
|
||||
int file_handle;
|
||||
void *current_mapping;
|
||||
off_t mapping_start;
|
||||
size_t mapping_length;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
wxMutex filemutex;
|
||||
wxFile file;
|
||||
PCMAudioProvider(const wxString &filename); // Create base object and open the file mapping
|
||||
virtual ~PCMAudioProvider(); // Closes the file mapping
|
||||
char * EnsureRangeAccessible(int64_t range_start, int64_t range_length); // Ensure that the given range of bytes are accessible in the file mapping and return a pointer to the first byte of the requested range
|
||||
|
||||
int64_t file_size; // Size of the opened file
|
||||
|
||||
// Hold data for an index point,
|
||||
// to support files where audio data are
|
||||
|
|
Loading…
Reference in a new issue