From 08910b5a9dd27eb879dc84a87a1c002ade99ee82 Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Mon, 24 Mar 2008 00:10:09 +0000 Subject: [PATCH] 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. --- aegisub/audio_provider_pcm.cpp | 186 +++++++++++++++++++++++++++++---- aegisub/audio_provider_pcm.h | 30 +++++- 2 files changed, 195 insertions(+), 21 deletions(-) diff --git a/aegisub/audio_provider_pcm.cpp b/aegisub/audio_provider_pcm.cpp index b91430f81..3eab0cada 100644 --- a/aegisub/audio_provider_pcm.cpp +++ b/aegisub/audio_provider_pcm.cpp @@ -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 +#ifndef _WINDOWS +#include +#include +#include +#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. diff --git a/aegisub/audio_provider_pcm.h b/aegisub/audio_provider_pcm.h index ea009e8ff..eeee5aeb4 100644 --- a/aegisub/audio_provider_pcm.h +++ b/aegisub/audio_provider_pcm.h @@ -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 #include +#ifdef _WINDOWS +#include +#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