implement workarounds for VBR mp3 problem

This commit is contained in:
David Rose 2005-01-18 19:16:59 +00:00
parent 00990fb767
commit 4ffa6f2864
6 changed files with 188 additions and 186 deletions

View File

@ -34,6 +34,24 @@ ConfigureFn(config_milesAudio) {
ConfigVariableBool miles_audio_force_midi_reset
("audio-force-midi-reset", true);
ConfigVariableInt miles_audio_expand_mp3_threshold
("miles-audio-expand-mp3-threshold", 16384,
PRC_DESC("This enables a Miles workaround in which small MP3 files are "
"expanded in-memory at load time into WAV format, which can "
"work around problems with Miles being unable to correctly "
"report the length of, or seek within, a variable bit-rate encoded "
"MP3 file. Any MP3 file whose length in bytes is less than "
"this value will be expanded."));
ConfigVariableInt miles_audio_calc_mp3_threshold
("miles-audio-calc-mp3-threshold", 1048576,
PRC_DESC("This is a second fallback for miles-audio-expand-mp3-threshold. "
"Any MP3 file whose length in bytes is less than this value "
"will have its length calculated on demand, by running through "
"the entire file first. This works around a Miles bug in "
"which variable bit-rate encoded MP3 files do not report an "
"accurate length."));
////////////////////////////////////////////////////////////////////
// Function: init_libMilesAudio
// Description: Initializes the library. This must be called at

View File

@ -24,12 +24,15 @@
#ifdef HAVE_RAD_MSS //[
#include "notifyCategoryProxy.h"
#include "configVariableBool.h"
#include "configVariableInt.h"
#include "dconfig.h"
ConfigureDecl(config_milesAudio, EXPCL_MILES_AUDIO, EXPTP_MILES_AUDIO);
NotifyCategoryDecl(milesAudio, EXPCL_MILES_AUDIO, EXPTP_MILES_AUDIO);
extern ConfigVariableBool miles_audio_force_midi_reset;
extern ConfigVariableInt miles_audio_expand_mp3_threshold;
extern ConfigVariableInt miles_audio_calc_mp3_threshold;
extern EXPCL_MILES_AUDIO void init_libMilesAudio();

View File

@ -154,13 +154,6 @@ MilesAudioManager() {
} else {
audio_info(" using Miles hardware midi");
}
if (use_vfs) {
AIL_set_file_callbacks(vfs_open_callback,
vfs_close_callback,
vfs_seek_callback,
vfs_read_callback);
}
} else {
audio_debug(" AIL_quick_startup failed: "<<AIL_last_error());
_is_valid = false;
@ -246,24 +239,82 @@ is_valid() {
////////////////////////////////////////////////////////////////////
// Function: MilesAudioManager::load
// Access:
// Description:
// Access: Private
// Description: Reads a sound file and allocates a SoundData pointer
// for it. Returns NULL if the sound file cannot be
// loaded.
////////////////////////////////////////////////////////////////////
HAUDIO MilesAudioManager::
PT(MilesAudioManager::SoundData) MilesAudioManager::
load(Filename file_name) {
HAUDIO audio;
if (use_vfs) {
audio = AIL_quick_load(file_name.c_str());
// We used to use callbacks to hook AIL_quick_load() into the vfs
// system directly. The theory was that that would enable
// AIL_quick_load() to stream the file from disk, avoiding the need
// to keep the whole thing in memory at once. But it turns out that
// AIL_quick_load() always reads the whole file anyway, so it's a
// moot point.
// Nowadays we don't mess around with that callback nonsense, and
// just read the whole file directly. Not only is it simpler, but
// preloading the sound files allows us to optionally convert MP3 to
// WAV format in-memory at load time.
PT(SoundData) sd = new SoundData;
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
if (!vfs->read_file(file_name, sd->_raw_data)) {
milesAudio_cat.warning()
<< "Unable to read " << file_name << "\n";
return NULL;
}
sd->_basename = file_name.get_basename();
sd->_file_type =
AIL_file_type(sd->_raw_data.data(), sd->_raw_data.size());
bool expand_to_wav = false;
if (sd->_file_type != AILFILETYPE_MPEG_L3_AUDIO) {
audio_debug(sd->_basename << " is not an mp3 file.");
} else if ((int)sd->_raw_data.size() >= miles_audio_expand_mp3_threshold) {
audio_debug(sd->_basename << " is too large to expand in-memory.");
} else {
string stmp = file_name.to_os_specific();
audio_debug(" \"" << stmp << "\"");
audio = AIL_quick_load(stmp.c_str());
expand_to_wav = true;
}
if (expand_to_wav) {
// Now convert the file to WAV format in-memory. This is useful
// to work around seek and length problems associated with
// variable bit-rate MP3 encoding.
void *wav_data;
U32 wav_data_size;
if (AIL_decompress_ASI(sd->_raw_data.data(), sd->_raw_data.size(),
sd->_basename.c_str(), &wav_data, &wav_data_size,
NULL)) {
audio_debug("expanded " << sd->_basename << " from " << sd->_raw_data.size()
<< " bytes to " << wav_data_size << " bytes.");
// Now copy the memory into our own buffers, and free the
// Miles-allocated memory.
sd->_raw_data.assign((char *)wav_data, wav_data_size);
AIL_mem_free_lock(wav_data);
sd->_file_type = AILFILETYPE_PCM_WAV;
} else {
audio_debug("unable to expand " << sd->_basename);
}
}
sd->_audio = AIL_quick_load_mem(sd->_raw_data.data(), sd->_raw_data.size());
if (!audio) {
if (!sd->_audio) {
audio_error(" MilesAudioManager::load failed "<< AIL_last_error());
return NULL;
}
return audio;
// We still need to keep around the raw data value, since
// AIL_quick_load_mem() doesn't make a copy.
return sd;
}
////////////////////////////////////////////////////////////////////
@ -283,34 +334,29 @@ get_sound(const string& file_name, bool) {
assert(is_valid());
Filename path = file_name;
if (use_vfs) {
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
vfs->resolve_filename(path, get_sound_path());
} else {
path.resolve_filename(get_sound_path());
}
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
vfs->resolve_filename(path, get_sound_path());
audio_debug(" resolved file_name is '"<<path<<"'");
HAUDIO audio=0;
PT(SoundData) sd;
// Get the sound, either from the cache or from a loader:
SoundMap::const_iterator si=_sounds.find(path);
if (si != _sounds.end()) {
// ...found the sound in the cache.
audio = (*si).second;
audio_debug(" sound found in pool 0x" << (void*)audio);
sd = (*si).second;
audio_debug(" sound found in pool 0x" << (void*)sd);
} else {
// ...the sound was not found in the cache/pool.
audio=load(path);
if (audio) {
sd = load(path);
if (sd != (SoundData *)NULL) {
while (_sounds.size() >= (unsigned int)_cache_limit) {
uncache_a_sound();
}
// Put it in the pool:
// The following is roughly like: _sounds[path] = audio;
// The following is roughly like: _sounds[path] = sd;
// But, it gives us an iterator into the map.
pair<SoundMap::const_iterator, bool> ib
=_sounds.insert(pair<string, HAUDIO>(path, audio));
=_sounds.insert(SoundMap::value_type(path, sd));
if (!ib.second) {
// The insert failed.
audio_debug(" failed map insert of "<<path);
@ -324,10 +370,10 @@ get_sound(const string& file_name, bool) {
}
// Create an AudioSound from the sound:
PT(AudioSound) audioSound = 0;
if (audio) {
if (sd != (SoundData *)NULL) {
most_recently_used((*si).first);
PT(MilesAudioSound) milesAudioSound
=new MilesAudioSound(this, audio, (*si).first);
=new MilesAudioSound(this, sd, (*si).first);
nassertr(milesAudioSound, 0);
milesAudioSound->set_active(_active);
_sounds_on_loan.insert(milesAudioSound);
@ -352,12 +398,8 @@ uncache_sound(const string& file_name) {
assert(is_valid());
Filename path = file_name;
if (use_vfs) {
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
vfs->resolve_filename(path, get_sound_path());
} else {
path.resolve_filename(get_sound_path());
}
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
vfs->resolve_filename(path, get_sound_path());
audio_debug(" path=\""<<path<<"\"");
SoundMap::iterator i=_sounds.find(path);
@ -366,7 +408,6 @@ uncache_sound(const string& file_name) {
LRU::iterator lru_i=find(_lru.begin(), _lru.end(), &(i->first));
assert(lru_i != _lru.end());
_lru.erase(lru_i);
AIL_quick_unload(i->second);
_sounds.erase(i);
}
assert(is_valid());
@ -390,7 +431,6 @@ uncache_a_sound() {
if (i != _sounds.end()) {
audio_debug(" uncaching \""<<i->first<<"\"");
AIL_quick_unload(i->second);
_sounds.erase(i);
}
assert(is_valid());
@ -424,10 +464,6 @@ void MilesAudioManager::
clear_cache() {
audio_debug("MilesAudioManager::clear_cache()");
if (_is_valid) { assert(is_valid()); }
SoundMap::iterator i=_sounds.begin();
for (; i!=_sounds.end(); ++i) {
AIL_quick_unload(i->second);
}
_sounds.clear();
_lru.clear();
if (_is_valid) { assert(is_valid()); }
@ -697,99 +733,75 @@ get_gm_file_path(string& result) {
}
////////////////////////////////////////////////////////////////////
// Function: MilesAudioManager::vfs_open_callback
// Access: Private, Static
// Description: A Miles callback to open a file for reading from the
// VFS system.
// Function: MilesAudioManager::SoundData::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
U32 MilesAudioManager::
vfs_open_callback(const char *filename, U32 *file_handle) {
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
istream *istr = vfs->open_read_file(filename);
if (istr == (istream *)NULL) {
// Unable to open.
milesAudio_cat.warning()
<< "Unable to open " << filename << "\n";
*file_handle = 0;
return 0;
}
// Successfully opened. Now we should return a U32 that we can
// map back into this istream pointer later. Strictly speaking,
// we should allocate a table of istream pointers and assign each
// one a unique number, but for now we'll cheat because we know
// that the Miles code (presently) only runs on Win32, which
// always has 32-bit pointers.
*file_handle = (U32)istr;
return 1;
MilesAudioManager::SoundData::
SoundData() :
_audio(0),
_has_length(false)
{
}
////////////////////////////////////////////////////////////////////
// Function: MilesAudioManager::vfs_read_callback
// Access: Private, Static
// Description: A Miles callback to read data from a file opened via
// vfs_open_callback().
// Function: MilesAudioManager::SoundData::Destructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
U32 MilesAudioManager::
vfs_read_callback(U32 file_handle, void *buffer, U32 bytes) {
if (file_handle == 0) {
// File was not opened.
return 0;
MilesAudioManager::SoundData::
~SoundData() {
if (_audio != 0) {
AIL_quick_unload(_audio);
}
istream *istr = (istream *)file_handle;
istr->read((char *)buffer, bytes);
size_t bytes_read = istr->gcount();
return bytes_read;
}
////////////////////////////////////////////////////////////////////
// Function: MilesAudioManager::vfs_seek_callback
// Access: Private, Static
// Description: A Miles callback to seek within a file opened via
// vfs_open_callback().
// Function: MilesAudioManager::SoundData::Destructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
S32 MilesAudioManager::
vfs_seek_callback(U32 file_handle, S32 offset, U32 type) {
if (file_handle == 0) {
// File was not opened.
return 0;
}
istream *istr = (istream *)file_handle;
float MilesAudioManager::SoundData::
get_length() {
if (!_has_length) {
// Time to determine the length of the file.
ios::seekdir dir = ios::beg;
switch (type) {
case AIL_FILE_SEEK_BEGIN:
dir = ios::beg;
break;
case AIL_FILE_SEEK_CURRENT:
dir = ios::cur;
break;
case AIL_FILE_SEEK_END:
dir = ios::end;
break;
if (_file_type == AILFILETYPE_MPEG_L3_AUDIO &&
(int)_raw_data.size() < miles_audio_calc_mp3_threshold) {
// If it's an mp3 file, we may not trust Miles to compute its
// length correctly (Miles doesn't correctly compute the length
// of VBR MP3 files). So in that case, decompress the whole
// file to determine its precise length.
audio_debug("Computing length of " << _basename);
void *wav_data;
U32 wav_data_size;
if (AIL_decompress_ASI(_raw_data.data(), _raw_data.size(),
_basename.c_str(), &wav_data, &wav_data_size,
NULL)) {
AILSOUNDINFO info;
if (AIL_WAV_info(wav_data, &info)) {
_length = (float)info.samples / (float)info.rate;
audio_debug(info.samples << " samples at " << info.rate
<< "; length is " << _length << " seconds.");
_has_length = true;
}
AIL_mem_free_lock(wav_data);
}
}
if (!_has_length) {
// If it's not an mp3 file, or we don't care about precalcing
// mp3 files, just ask Miles to do it.
_length = ((float)AIL_quick_ms_length(_audio)) * 0.001f;
audio_debug("Miles reports length of " << _length
<< " for " << _basename);
}
}
istr->seekg(offset, dir);
return istr->tellg();
return _length;
}
////////////////////////////////////////////////////////////////////
// Function: MilesAudioManager::vfs_close_callback
// Access: Private, Static
// Description: A Miles callback to close a file opened via
// vfs_open_callback().
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
vfs_close_callback(U32 file_handle) {
if (file_handle == 0) {
// File was not opened.
return;
}
istream *istr = (istream *)file_handle;
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
vfs->close_read_file(istr);
}
#endif //]

View File

@ -65,7 +65,20 @@ public:
private:
// The sound cache:
typedef pmap<string, HAUDIO > SoundMap;
class SoundData : public ReferenceCount {
public:
SoundData();
~SoundData();
float get_length();
string _basename;
HAUDIO _audio;
S32 _file_type;
string _raw_data;
bool _has_length;
float _length; // in seconds.
};
typedef pmap<string, PT(SoundData) > SoundMap;
SoundMap _sounds;
typedef pset<MilesAudioSound* > AudioSet;
@ -90,7 +103,7 @@ private:
bool _is_valid;
bool _hasMidiSounds;
HAUDIO load(Filename file_name);
PT(SoundData) load(Filename file_name);
// Tell the manager that the sound dtor was called.
void release_sound(MilesAudioSound* audioSound);
@ -110,13 +123,6 @@ private:
void force_midi_reset();
// These are "callback" functions that implement vfs-style I/O for
// Miles.
static U32 AILCALLBACK vfs_open_callback(const char *filename, U32 *file_handle);
static U32 AILCALLBACK vfs_read_callback(U32 file_handle, void *buffer, U32 bytes);
static S32 AILCALLBACK vfs_seek_callback(U32 file_handle, S32 offset, U32 type);
static void AILCALLBACK vfs_close_callback(U32 file_handle);
friend class MilesAudioSound;

View File

@ -160,17 +160,17 @@ namespace {
////////////////////////////////////////////////////////////////////
MilesAudioSound::
MilesAudioSound(MilesAudioManager* manager,
HAUDIO audio, string file_name, float length)
: _manager(manager), _file_name(file_name),
MilesAudioManager::SoundData *sd, string file_name, float length)
: _sd(sd), _manager(manager), _file_name(file_name),
_volume(1.0f), _balance(0),
_loop_count(1), _length(length),
_active(true), _paused(false) {
nassertv(audio);
nassertv(sd != NULL);
nassertv(!file_name.empty());
audio_debug("MilesAudioSound(manager=0x"<<(void*)&manager
<<", audio=0x"<<(void*)audio<<", file_name="<<file_name<<")");
<<", sd=0x"<<(void*)sd<<", file_name="<<file_name<<")");
// Make our own copy of the sound header data:
_audio=AIL_quick_copy(audio);
_audio=AIL_quick_copy(sd->_audio);
}
////////////////////////////////////////////////////////////////////
@ -232,10 +232,9 @@ stop() {
// The _paused flag should not be cleared here. _paused is not like
// the Pause button on a cd/dvd player. It is used as a flag to say
// that it was looping when it was set inactive. There is no need to
// make this symetrical with play(). set_active() is the 'owner' of
// make this symmetrical with play(). set_active() is the 'owner' of
// _paused. play() accesses _paused to help in the situation where
// someone calls play on an inactive sound().
// removing --> _paused=false;
AIL_quick_halt(_audio);
}
@ -329,8 +328,8 @@ set_time(float time) {
time = max_time;
}
S32 milisecond_time=S32(1000*time);
AIL_quick_set_ms_position(_audio, milisecond_time);
S32 millisecond_time=S32(1000*time);
AIL_quick_set_ms_position(_audio, millisecond_time);
}
////////////////////////////////////////////////////////////////////
@ -340,8 +339,8 @@ set_time(float time) {
////////////////////////////////////////////////////////////////////
float MilesAudioSound::
get_time() const {
S32 milisecond_time=AIL_quick_ms_position(_audio);
float time=float(milisecond_time*.001);
S32 millisecond_time=AIL_quick_ms_position(_audio);
float time=float(millisecond_time*.001);
miles_audio_debug("get_time() returning "<<time);
return time;
}
@ -355,7 +354,7 @@ void MilesAudioSound::
set_volume(float volume) {
miles_audio_debug("set_volume(volume="<<volume<<")");
// *Set the volume even if our volume is not changing, because the
// *MilesAudioManager will call set_volume when *its* volume changes.
// MilesAudioManager will call set_volume when *its* volume changes.
// Set the volume:
_volume=volume;
// Account for the category of sound:
@ -440,44 +439,7 @@ get_balance() const {
////////////////////////////////////////////////////////////////////
float MilesAudioSound::
length() const {
if (_length == 0.0f) {
#ifndef NEED_MILES_LENGTH_WORKAROUND
_length=((float)AIL_quick_ms_length(_audio))*0.001f;
if (_length == 0.0f) {
audio_error("ERROR: Miles returned length 0 for "<<_file_name << "!");
}
#else
// hack:
// For now, the sound needs to be playing, in order to
// get the right length. I'm in contact with RAD about the problem. I've
// sent them example code. They've told me they're looking into it.
// Until then, we'll play the sound to get the length.
// Miles 6.5c note: seems to be fixed for .mid, .mp3 also seems mostly fixed,
// but not 100% positive (need to look for errors with CATCH_ERROR on)
// #define CATCH_MILES_LENGTH_ERROR
#ifdef CATCH_MILES_LENGTH_ERROR
_length=((float)AIL_quick_ms_length(_audio))*0.001f;
if (_length == 0.0f) {
audio_error("ERROR: Miles returned length 0 for "<<_file_name << "!");
exit(1);
}
#endif
if (AIL_quick_status(_audio)==QSTAT_PLAYING) {
_length=((float)AIL_quick_ms_length(_audio))*0.001f;
} else {
AIL_quick_play(_audio, 1);
_length=((float)AIL_quick_ms_length(_audio))*0.001f;
AIL_quick_halt(_audio);
}
#endif
}
//audio_cat->info() << "MilesAudioSound::length() returning " << _length << endl;
audio_debug("MilesAudioSound::length() returning "<<_length);
return _length;
return _sd->get_length();
}
////////////////////////////////////////////////////////////////////

View File

@ -116,6 +116,7 @@ public:
void finished();
private:
PT(MilesAudioManager::SoundData) _sd;
HAUDIO _audio;
PT(MilesAudioManager) _manager;
float _volume; // 0..1.0
@ -142,7 +143,7 @@ private:
bool _paused;
MilesAudioSound(MilesAudioManager* manager,
HAUDIO audio, string file_name, float length=0.0f);
MilesAudioManager::SoundData *sd, string file_name, float length=0.0f);
friend class MilesAudioManager;