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:
Niels Martin Hansen 2008-03-24 00:10:09 +00:00
parent 3fb93565f5
commit 08910b5a9d
2 changed files with 195 additions and 21 deletions

View file

@ -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.

View file

@ -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