diff --git a/panda/src/audiotraits/config_milesAudio.cxx b/panda/src/audiotraits/config_milesAudio.cxx index bf04cb46b0..3f144fbc66 100644 --- a/panda/src/audiotraits/config_milesAudio.cxx +++ b/panda/src/audiotraits/config_milesAudio.cxx @@ -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 diff --git a/panda/src/audiotraits/config_milesAudio.h b/panda/src/audiotraits/config_milesAudio.h index e495a43ac0..0342d3dc42 100644 --- a/panda/src/audiotraits/config_milesAudio.h +++ b/panda/src/audiotraits/config_milesAudio.h @@ -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(); diff --git a/panda/src/audiotraits/milesAudioManager.cxx b/panda/src/audiotraits/milesAudioManager.cxx index 0a3ff7415d..4d7d57a518 100644 --- a/panda/src/audiotraits/milesAudioManager.cxx +++ b/panda/src/audiotraits/milesAudioManager.cxx @@ -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: "<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 '"<= (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 ib - =_sounds.insert(pair(path, audio)); + =_sounds.insert(SoundMap::value_type(path, sd)); if (!ib.second) { // The insert failed. audio_debug(" failed map insert of "<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=\""<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 \""<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 //] diff --git a/panda/src/audiotraits/milesAudioManager.h b/panda/src/audiotraits/milesAudioManager.h index e78891b4bb..1ac2735a7d 100644 --- a/panda/src/audiotraits/milesAudioManager.h +++ b/panda/src/audiotraits/milesAudioManager.h @@ -65,7 +65,20 @@ public: private: // The sound cache: - typedef pmap 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 SoundMap; SoundMap _sounds; typedef pset 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; diff --git a/panda/src/audiotraits/milesAudioSound.cxx b/panda/src/audiotraits/milesAudioSound.cxx index bd74cd3867..eb2c64a2f6 100644 --- a/panda/src/audiotraits/milesAudioSound.cxx +++ b/panda/src/audiotraits/milesAudioSound.cxx @@ -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="<_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 "<info() << "MilesAudioSound::length() returning " << _length << endl; - audio_debug("MilesAudioSound::length() returning "<<_length); - return _length; + return _sd->get_length(); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/audiotraits/milesAudioSound.h b/panda/src/audiotraits/milesAudioSound.h index 80663a1788..a8c1486155 100644 --- a/panda/src/audiotraits/milesAudioSound.h +++ b/panda/src/audiotraits/milesAudioSound.h @@ -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;