Browse Source

implement workarounds for VBR mp3 problem

David Rose 21 years ago
parent
commit
4ffa6f2864

+ 18 - 0
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

+ 3 - 0
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();
 

+ 138 - 126
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: "<<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;
-
-  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;
-  }
+float MilesAudioManager::SoundData::
+get_length() {
+  if (!_has_length) {
+    // Time to determine the length of the file.
+
+    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;
+        }
 
-  istr->seekg(offset, dir);
-  return istr->tellg();
-}
+        AIL_mem_free_lock(wav_data);
+      }
+    }
 
-////////////////////////////////////////////////////////////////////
-//     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;
+    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);
+    }
   }
-  istream *istr = (istream *)file_handle;
-  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
-  vfs->close_read_file(istr);
-}
 
+  return _length;
+}
 
 #endif //]

+ 15 - 9
panda/src/audiotraits/milesAudioManager.h

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

+ 12 - 50
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="<<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();
 }
 
 ////////////////////////////////////////////////////////////////////

+ 2 - 1
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;