Browse Source

added streaming audio support

Josh Yelon 17 years ago
parent
commit
16b6c060a8

+ 4 - 0
panda/src/movies/Sources.pp

@@ -26,6 +26,7 @@
         userDataAudio.h userDataAudio.I \
         userDataAudioCursor.h userDataAudioCursor.I \
         webcamVideo.h webcamVideo.I \
+        microphoneAudio.h microphoneAudio.I \
 	config_movies.h
     
   #define INCLUDED_SOURCES \
@@ -44,6 +45,8 @@
         userDataAudioCursor.cxx \
 	webcamVideo.cxx \
 	webcamVideoDS.cxx \
+        microphoneAudio.cxx \
+        microphoneAudioDS.cxx \
         config_movies.cxx
     
   #define INSTALL_HEADERS \
@@ -59,6 +62,7 @@
         ffmpegAudioCursor.h ffmpegAudioCursor.I \
         ffmpegVirtualFile.h ffmpegVirtualFile.I \
         webcamVideo.h webcamVideo.I \
+        microphoneAudio.h microphoneAudio.I \
 	config_movies.h
 
   #define IGATESCAN all

+ 1 - 0
panda/src/movies/config_movies.cxx

@@ -56,6 +56,7 @@ init_libmovies() {
   UserDataAudio::init_type();
   UserDataAudioCursor::init_type();
   WebcamVideo::init_type();
+  MicrophoneAudio::init_type();
 #ifdef HAVE_FFMPEG
   FfmpegVideo::init_type();
   FfmpegVideoCursor::init_type();

+ 38 - 0
panda/src/movies/microphoneAudio.I

@@ -0,0 +1,38 @@
+// Filename: microphoneAudio.I
+// Created by: jyelon (02Jul07)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////
+//     Function: MicrophoneAudio::get_channels
+//       Access: Published
+//  Description: Returns the number of channels.
+////////////////////////////////////////////////////////////////////
+INLINE int MicrophoneAudio::
+get_channels() const {
+  return _channels;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MicrophoneAudio::get_rate
+//       Access: Published
+//  Description: Returns the sample rate.
+////////////////////////////////////////////////////////////////////
+INLINE int MicrophoneAudio::
+get_rate() const {
+  return _rate;
+}
+

+ 90 - 0
panda/src/movies/microphoneAudio.cxx

@@ -0,0 +1,90 @@
+// Filename: microphoneAudio.cxx
+// Created by: jyelon (02Jul07)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2007, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] 
+//
+////////////////////////////////////////////////////////////////////
+
+#include "microphoneAudio.h"
+#include "movieAudioCursor.h"
+
+pvector<PT(MicrophoneAudio)> MicrophoneAudio::_all_microphones;
+TypeHandle MicrophoneAudio::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: MicrophoneAudio::Destructor
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+MicrophoneAudio::
+~MicrophoneAudio() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MicrophoneAudio::find_all_microphones
+//       Access: Public
+//  Description: Scans the hardware for microphones, and pushes them
+//               onto the global list of all microphones.
+//
+//               There are several implementations of MicrophoneAudio,
+//               including one based on DirectShow, one based on
+//               Linux ALSA, and so forth.  These implementations
+//               are contained in one C++ file each, and they export
+//               nothing at all except a single "find_all" function.
+//               Otherwise, they can only be accessed through the
+//               virtual methods of the MicrophoneAudio objects they
+//               create.
+////////////////////////////////////////////////////////////////////
+void MicrophoneAudio::
+find_all_microphones() {
+  static bool initialized = false;
+  if (initialized) return;
+  initialized = true;
+
+#ifdef HAVE_DIRECTCAM
+  extern void find_all_microphones_ds();
+  find_all_microphones_ds();
+#endif
+
+#ifdef HAVE_ALSA
+  extern void find_all_microphones_alsa();
+  find_all_microphones_alsa();
+#endif
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MicrophoneAudio::get_num_options
+//       Access: Public
+//  Description: Returns the number of microphone options.  An "option"
+//               consists of a device plus a set of configuration
+//               parameters.  For example, "Soundblaster Audigy Line in
+//               at 44,100 samples/sec" would be an option.
+////////////////////////////////////////////////////////////////////
+int MicrophoneAudio::
+get_num_options() {
+  find_all_microphones();
+  return _all_microphones.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MicrophoneAudio::get_option
+//       Access: Public
+//  Description: Returns the nth microphone option.
+////////////////////////////////////////////////////////////////////
+PT(MicrophoneAudio) MicrophoneAudio::
+get_option(int n) {
+  find_all_microphones();
+  nassertr((n >= 0) && (n < (int)_all_microphones.size()), NULL);
+  return _all_microphones[n];
+}

+ 73 - 0
panda/src/movies/microphoneAudio.h

@@ -0,0 +1,73 @@
+// Filename: microphoneAudio.h
+// Created by: jyelon (02Jul07)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef MICROPHONEAUDIO_H
+#define MICROPHONEAUDIO_H
+
+#include "movieAudio.h"
+class MovieAudio;
+class MovieAudioCursor;
+
+////////////////////////////////////////////////////////////////////
+//       Class : MicrophoneAudio
+// Description : Class MicrophoneAudio provides the means to read
+//               raw audio samples from a microphone.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA_MOVIES MicrophoneAudio : public MovieAudio {
+
+ PUBLISHED:
+  virtual ~MicrophoneAudio();
+
+  static int                 get_num_options();
+  static PT(MicrophoneAudio) get_option(int n);
+  
+  INLINE int get_channels() const;
+  INLINE int get_rate() const;
+  
+  virtual PT(MovieAudioCursor) open() = 0;
+
+public:
+  static void find_all_microphones();
+
+protected:
+  int _channels;
+  int _rate;
+
+  static pvector<PT(MicrophoneAudio)> _all_microphones;
+
+ public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedWritableReferenceCount::init_type();
+    register_type(_type_handle, "MicrophoneAudio",
+                  MovieAudio::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+ private:
+  static TypeHandle _type_handle;
+};
+
+#include "microphoneAudio.I"
+
+#endif

+ 422 - 0
panda/src/movies/microphoneAudioDS.cxx

@@ -0,0 +1,422 @@
+// Filename: microphoneAudioDS.cxx
+// Created by: jyelon (01Nov2007)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2007, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+//
+// It goes against Panda3D coding style conventions to hide an
+// entire class in a C++ file and not expose it through header
+// files at all.  However, in this case, these classes are so full
+// of OS-specific junk that I feel it is better to hide them
+// entirely.  - Josh
+//
+////////////////////////////////////////////////////////////////////
+
+#ifdef HAVE_DIRECTCAM
+
+#define WIN32_LEAN_AND_MEAN 
+
+#undef Configure
+
+#include <windows.h>
+#include <mmsystem.h>
+
+////////////////////////////////////////////////////////////////////
+//       Class : MicrophoneAudioDS
+// Description : The directshow implementation of microphones.
+////////////////////////////////////////////////////////////////////
+
+class MicrophoneAudioDS : public MicrophoneAudio
+{
+public:
+  static void find_all_microphones_ds();
+  friend void find_all_microphones_ds();
+
+private:
+  virtual PT(MovieAudioCursor) open();
+
+  int _device_id;
+  int _manufacturer_id;
+  int _product_id;
+
+  struct AudioBuf {
+    HGLOBAL   _storage_gh;
+    HGLOBAL   _header_gh;
+    LPSTR     _storage;
+    LPWAVEHDR _header;
+  };
+  typedef pvector <AudioBuf> AudioBuffers;
+
+  static void delete_buffers(AudioBuffers &buffers);
+  
+  friend class MicrophoneAudioCursorDS;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    MicrophoneAudio::init_type();
+    register_type(_type_handle, "MicrophoneAudioDS",
+                  MicrophoneAudio::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+TypeHandle MicrophoneAudioDS::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//       Class : MicrophoneAudioCursorDS
+// Description : The directshow implementation of microphones.
+////////////////////////////////////////////////////////////////////
+
+class MicrophoneAudioCursorDS : public MovieAudioCursor
+{
+public:
+  typedef MicrophoneAudioDS::AudioBuffers AudioBuffers;
+  MicrophoneAudioCursorDS(MicrophoneAudioDS *src, AudioBuffers &bufs, HWAVEIN hwav);
+  virtual ~MicrophoneAudioCursorDS();
+  
+  AudioBuffers _buffers;
+  HWAVEIN _hwavein;
+  int _samples_per_buffer;
+
+public:
+  virtual void read_samples(int n, PN_int16 *data);
+  virtual int ready() const;
+
+public:
+  void cleanup();
+
+  HWAVEIN _handle;
+  int     _next;    // Which buffer is the next one to read from.
+  int     _offset;  // How many samples to skip in the buffer.
+  
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    MovieAudioCursor::init_type();
+    register_type(_type_handle, "MicrophoneAudioCursorDS",
+                  MovieAudioCursor::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+TypeHandle MicrophoneAudioCursorDS::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: MicrophoneAudioDS::find_all_microphones_ds
+//       Access: Public, Static
+//  Description: Finds all DirectShow microphones and adds them to 
+//               the global list _all_microphones.
+////////////////////////////////////////////////////////////////////
+void MicrophoneAudioDS::
+find_all_microphones_ds() {
+  MMRESULT stat;
+  static int freqs[] = { 11025, 22050, 44100, 48000, 0 };
+
+  int ndevs = waveInGetNumDevs();
+  for (int i=0; i<ndevs; i++) {
+    WAVEINCAPS caps;
+    stat = waveInGetDevCaps(i, &caps, sizeof(caps));
+    if (stat != MMSYSERR_NOERROR) continue;
+    for (int chan=1; chan<=2; chan++) {
+      for (int fselect=0; freqs[fselect]; fselect++) {
+        WAVEFORMATEX format;
+        int freq = freqs[fselect];
+        format.wFormatTag = WAVE_FORMAT_PCM;
+        format.nChannels = chan;
+        format.nSamplesPerSec = freq;
+        format.nAvgBytesPerSec = freq * chan * 2;
+        format.nBlockAlign = 2 * chan;
+        format.wBitsPerSample = 16;
+        format.cbSize = 0;
+        stat = waveInOpen(NULL, i, &format, NULL, NULL, WAVE_FORMAT_QUERY);
+        if (stat == MMSYSERR_NOERROR) {
+          PT(MicrophoneAudioDS) p = new MicrophoneAudioDS();
+          ostringstream name;
+          name << "WaveIn: " << caps.szPname << " Chan:" << chan << " HZ:" << freq;
+          p->set_name(name.str());
+          p->_device_id = i;
+          p->_manufacturer_id = caps.wMid;
+          p->_product_id = caps.wPid;
+          p->_rate = freq;
+          p->_channels = chan;
+          _all_microphones.push_back((MicrophoneAudioDS*)p);
+        }
+      }
+    }
+  }
+}
+
+void find_all_microphones_ds() {
+  MicrophoneAudioDS::init_type();
+  //  MicrophoneAudioCursorDS::init_type();
+  MicrophoneAudioDS::find_all_microphones_ds();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MicrophoneAudioDS::delete_buffers
+//       Access: Private, Static
+//  Description: Delete a set of audio buffers.
+////////////////////////////////////////////////////////////////////
+void MicrophoneAudioDS::
+delete_buffers(AudioBuffers &buffers) {
+  for (int i=0; i<(int)buffers.size(); i++) {
+    AudioBuf &buf = buffers[i];
+    if (buf._header_gh) {
+      GlobalUnlock(buf._header_gh);
+      GlobalFree(buf._header_gh);
+    }
+    if (buf._storage_gh) {
+      GlobalUnlock(buf._storage_gh);
+      GlobalFree(buf._storage_gh);
+    }
+  }
+  buffers.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MicrophoneAudioDS::open
+//       Access: Published, Virtual
+//  Description: Open this video, returning a MovieVideoCursor.
+////////////////////////////////////////////////////////////////////
+PT(MovieAudioCursor) MicrophoneAudioDS::
+open() {
+  
+  // Allocate the buffers. 64 buffers, not quite 1/20 sec each.
+  int samples;
+  switch (_rate) {
+  case 11025: samples=512; break;
+  case 22050: samples=1024; break;
+  case 44100: samples=2048; break;
+  }
+  int bytes = _channels * samples * 2;
+
+  bool failed = false;
+  AudioBuffers buffers;
+  for (int i=0; i<64; i++) {
+    AudioBuf buf;
+    buf._storage_gh = 0;
+    buf._header_gh = 0;
+    buf._storage = 0;
+    buf._header = 0;
+
+    buf._storage_gh = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, bytes);
+    buf._header_gh = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, sizeof(WAVEHDR));
+    if (buf._storage_gh != 0) {
+      buf._storage = (LPSTR)GlobalLock(buf._storage_gh);
+    }
+    if (buf._header_gh != 0) {
+      buf._header = (LPWAVEHDR)GlobalLock(buf._header_gh);
+    }
+    if (buf._storage && buf._header) {
+      ZeroMemory(buf._header, sizeof(WAVEHDR));
+      buf._header->lpData = buf._storage;
+      buf._header->dwBufferLength = bytes;
+    } else {
+      failed = true;
+    }
+    buffers.push_back(buf);
+    if (failed) break;
+  }
+
+  if (failed) {
+    delete_buffers(buffers);
+    nassert_raise("Could not allocate audio input buffers.");
+    return NULL;
+  }
+  
+  WAVEFORMATEX format;
+  format.wFormatTag = WAVE_FORMAT_PCM;
+  format.nChannels = _channels;
+  format.nSamplesPerSec = _rate;
+  format.nAvgBytesPerSec = _rate * _channels * 2;
+  format.nBlockAlign = 2 * _channels;
+  format.wBitsPerSample = 16;
+  format.cbSize = 0;
+
+  HWAVEIN hwav;
+  MMRESULT stat = waveInOpen(&hwav, _device_id, &format, NULL, NULL, CALLBACK_NULL);
+  
+  if (stat != MMSYSERR_NOERROR) {
+    delete_buffers(buffers);
+    nassert_raise("Could not open audio input device.");
+    return NULL;
+  }
+
+  for (int i=0; i<(int)buffers.size(); i++) {
+    stat = waveInPrepareHeader(hwav, buffers[i]._header, sizeof(WAVEHDR));
+    if (stat == MMSYSERR_NOERROR) {
+      stat = waveInAddBuffer(hwav, buffers[i]._header, sizeof(WAVEHDR));
+    }
+    if (stat != MMSYSERR_NOERROR) {
+      waveInClose(hwav);
+      delete_buffers(buffers);
+      nassert_raise("Could not queue buffers for audio input device.");
+      return NULL;
+    }
+  }
+  stat = waveInStart(hwav);
+  if (stat != MMSYSERR_NOERROR) {
+    waveInClose(hwav);
+    delete_buffers(buffers);
+    nassert_raise("Could not start recording on input device.");
+    return NULL;    
+  }
+  return new MicrophoneAudioCursorDS(this, buffers, hwav);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MicrophoneAudioCursorDS::Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+MicrophoneAudioCursorDS::
+MicrophoneAudioCursorDS(MicrophoneAudioDS *src, AudioBuffers &bufs, HWAVEIN hwav) :
+  MovieAudioCursor(src),
+  _buffers(bufs),
+  _handle(hwav),
+  _next(0),
+  _offset(0)
+{
+  _audio_rate = src->get_rate();
+  _audio_channels = src->get_channels();
+  _length = 1.0E10;
+  _can_seek = false;
+  _can_seek_fast = false;
+  _aborted = false;
+  _samples_per_buffer = bufs[0]._header->dwBufferLength / (2 * _audio_channels);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MicrophoneAudioCursorDS::cleanup
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void MicrophoneAudioCursorDS::
+cleanup() {
+  if (_handle) {
+    waveInClose(_handle);
+    _handle = 0;
+  }
+  MicrophoneAudioDS::delete_buffers(_buffers);
+  _next = 0;
+  _offset = 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MicrophoneAudioCursorDS::Destructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+MicrophoneAudioCursorDS::
+~MicrophoneAudioCursorDS() {
+  cleanup();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MicrophoneAudioCursorDS::fetch_into_buffer
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void MicrophoneAudioCursorDS::
+read_samples(int n, PN_int16 *data) {
+  int orign = n;
+  if (_handle) {
+    while (1) {
+      int index = _next % _buffers.size();
+      if ((_buffers[index]._header->dwFlags & WHDR_DONE)==0) {
+        break;
+      }
+      
+      // Find start of data in buffer.
+      PN_int16 *src = (PN_int16*)(_buffers[index]._storage);
+      src += (_offset * _audio_channels);
+      
+      // Decide how many samples to extract from this buffer.
+      int samples = _samples_per_buffer;
+      samples -= _offset;
+      if (samples > n) samples = n;
+      
+      // Copy data to output buffer.
+      memcpy(data, src, samples * 2 * _audio_channels);
+      
+      // Advance pointers.
+      data += samples * _audio_channels;
+      n -= samples;
+      _offset += samples;
+      _samples_read += samples;
+      if (_offset != _samples_per_buffer) {
+        break;
+      }
+      _buffers[index]._header->dwFlags &= ~(WHDR_DONE);
+      MMRESULT stat = waveInUnprepareHeader(_handle, _buffers[index]._header, sizeof(WAVEHDR));
+      if (stat == MMSYSERR_NOERROR) {
+        stat = waveInPrepareHeader(_handle, _buffers[index]._header, sizeof(WAVEHDR));
+      }
+      if (stat == MMSYSERR_NOERROR) {
+        stat = waveInAddBuffer(_handle, _buffers[index]._header, sizeof(WAVEHDR));
+      }
+      if (stat != MMSYSERR_NOERROR) {
+        movies_cat.error() << "Could not requeue audio buffers, closing microphone.\n";
+        cleanup();
+        break;
+      }
+      _next += 1;
+      _offset = 0;
+    }
+  }
+  if (n > 0) {
+    memcpy(data, 0, n*2*_audio_channels);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MicrophoneAudioCursorDS::ready
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+int MicrophoneAudioCursorDS::
+ready() const {
+  if (_handle == 0) return 0;
+  int total = 0;
+  for (int i=0; i<(int)_buffers.size(); i++) {
+    int index = (_next + i) % (_buffers.size());
+    if ((_buffers[index]._header->dwFlags & WHDR_DONE)==0) {
+      break;
+    }
+    total += _samples_per_buffer;
+  }
+  total -= _offset;
+  return total;
+}
+
+
+#endif // HAVE_DIRECTSHOW

+ 1 - 35
panda/src/movies/movieAudioCursor.I

@@ -74,40 +74,6 @@ length() const {
   return _length;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: MovieAudioCursor::ready
-//       Access: Public
-//  Description: Returns the number of audio samples that are ready
-//               to read.  This is primarily relevant for sources like
-//               microphones which produce samples at a fixed rate.
-//               If you try to read more samples than are ready, the
-//               result will be silent samples.  
-//
-//               Some audio streams do not have a limit on how fast 
-//               they can produce samples.  Such streams will always
-//               return 0x40000000 as the ready-count.  This may well
-//               exceed the length of the audio stream.  You therefore
-//               need to check length separately.
-//
-//               If the aborted flag is set, that means the ready count
-//               is no longer being replenished.  For example, a
-//               MovieAudioCursor might be reading from an internet
-//               radio station, and it might buffer data to avoid 
-//               underruns.  If it loses connection to the radio
-//               station, it will set the aborted flag to indicate that
-//               the buffer is no longer being replenished.  But it is
-//               still ok to read the samples that are in the buffer,
-//               at least until they run out.  Once those are gone,
-//               there will be no more.
-//
-//               An audio consumer needs to check the length, the
-//               ready status, and the aborted flag.
-////////////////////////////////////////////////////////////////////
-INLINE int MovieAudioCursor::
-ready() const {
-  return _ready;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: MovieAudioCursor::can_seek
 //       Access: Public
@@ -165,6 +131,6 @@ tell() const {
 ////////////////////////////////////////////////////////////////////
 INLINE void MovieAudioCursor::
 skip_samples(int n) {
-  read_samples(n, 0);
+  read_samples(n, (PN_int16*)0);
 }
  

+ 92 - 2
panda/src/movies/movieAudioCursor.cxx

@@ -36,7 +36,6 @@ MovieAudioCursor(MovieAudio *src) :
   _length(1.0E10),
   _can_seek(true),
   _can_seek_fast(true),
-  _ready(0x40000000),
   _aborted(false),
   _samples_read(0)
 {
@@ -69,12 +68,69 @@ read_samples(int n, PN_int16 *data) {
     return;
   }
 
-  for (int i=0; i<n; i++) {
+  int desired = n * _audio_channels;
+  for (int i=0; i<desired; i++) {
     data[i] = 0;
   }
   _samples_read += n;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: MovieAudioCursor::read_samples
+//       Access: Published
+//  Description: Read audio samples from the stream into a 
+//               Datagram.  N is the number of samples you wish
+//               to read. Multiple-channel audio will be interleaved. 
+//
+//               This is not particularly efficient, but it may be
+//               a convenient way to manipulate samples in python.
+////////////////////////////////////////////////////////////////////
+void MovieAudioCursor::
+read_samples(int n, Datagram *dg) {
+  PN_int16 tmp[4096];
+  while (n > 0) {
+    int blocksize = (4096 / _audio_channels);
+    if (blocksize > n) blocksize = n;
+    int words = blocksize * _audio_channels;
+    read_samples(blocksize, tmp);
+    for (int i=0; i<words; i++) {
+      dg->add_int16(tmp[i]);
+    }
+    n -= blocksize;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MovieAudioCursor::read_samples
+//       Access: Published
+//  Description: Read audio samples from the stream and returns
+//               them as a string.  The samples are stored little-endian
+//               in the string.  N is the number of samples you wish
+//               to read.  Multiple-channel audio will be interleaved. 
+//
+//               This is not particularly efficient, but it may be
+//               a convenient way to manipulate samples in python.
+////////////////////////////////////////////////////////////////////
+string MovieAudioCursor::
+read_samples(int n) {
+  ostringstream result;
+  PN_int16 tmp[4096];
+  while (n > 0) {
+    int blocksize = (4096 / _audio_channels);
+    if (blocksize > n) blocksize = n;
+    int words = blocksize * _audio_channels;
+    read_samples(blocksize, tmp);
+    for (int i=0; i<words; i++) {
+      PN_int16 word = tmp[i];
+      result.put((char)(word & 255));
+      result.put((char)((word>>8) & 255));
+    }
+    n -= blocksize;
+  }
+  return result.str();
+}
+
+
 ////////////////////////////////////////////////////////////////////
 //     Function: MovieAudioCursor::seek
 //       Access: Published, Virtual
@@ -103,3 +159,37 @@ seek(double offset) {
   _samples_read = 0;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: MovieAudioCursor::ready
+//       Access: Public
+//  Description: Returns the number of audio samples that are ready
+//               to read.  This is primarily relevant for sources like
+//               microphones which produce samples at a fixed rate.
+//               If you try to read more samples than are ready, the
+//               result will be silent samples.  
+//
+//               Some audio streams do not have a limit on how fast 
+//               they can produce samples.  Such streams will always
+//               return 0x40000000 as the ready-count.  This may well
+//               exceed the length of the audio stream.  You therefore
+//               need to check length separately.
+//
+//               If the aborted flag is set, that means the ready count
+//               is no longer being replenished.  For example, a
+//               MovieAudioCursor might be reading from an internet
+//               radio station, and it might buffer data to avoid 
+//               underruns.  If it loses connection to the radio
+//               station, it will set the aborted flag to indicate that
+//               the buffer is no longer being replenished.  But it is
+//               still ok to read the samples that are in the buffer,
+//               at least until they run out.  Once those are gone,
+//               there will be no more.
+//
+//               An audio consumer needs to check the length, the
+//               ready status, and the aborted flag.
+////////////////////////////////////////////////////////////////////
+int MovieAudioCursor::
+ready() const {
+  return 0x40000000;
+}
+

+ 5 - 4
panda/src/movies/movieAudioCursor.h

@@ -50,20 +50,21 @@ PUBLISHED:
   INLINE double length() const;
   INLINE bool can_seek() const;
   INLINE bool can_seek_fast() const;
-  INLINE bool aborted() const;
   INLINE double tell() const;
   INLINE void skip_samples(int n);
-  INLINE int ready() const;
+  INLINE bool aborted() const;
+  virtual int ready() const;
   virtual void seek(double offset);
+  void read_samples(int n, Datagram *dg);
+  string read_samples(int n);
 
 public:
   virtual void read_samples(int n, PN_int16 *data);
-  
+
 protected:
   PT(MovieAudio) _source;
   int _audio_rate;
   int _audio_channels;
-  int _ready;
   double _length;
   bool _can_seek;
   bool _can_seek_fast;

+ 3 - 0
panda/src/movies/movies_composite1.cxx

@@ -21,4 +21,7 @@
 #include "webcamVideo.cxx"
 #include "webcamVideoDS.cxx"
 
+#include "microphoneAudio.cxx"
+#include "microphoneAudioDS.cxx"
+
 #include "config_movies.cxx"

+ 43 - 33
panda/src/movies/userDataAudio.cxx

@@ -46,19 +46,6 @@ UserDataAudio::
 ~UserDataAudio() {
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: UserDataAudio::update_cursor
-//       Access: Private
-//  Description: Make sure that the UserDataAudioCursor's ready
-//               and aborted status flags are correct.
-////////////////////////////////////////////////////////////////////
-void UserDataAudio::
-update_cursor() {
-  if (_cursor == 0) return;
-  _cursor->_ready = _data.size();
-  _cursor->_aborted = _aborted;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: UserDataAudio::open
 //       Access: Published, Virtual
@@ -73,7 +60,6 @@ open() {
     return NULL;
   }
   _cursor = new UserDataAudioCursor(this);
-  update_cursor();
   return _cursor;
 }
 
@@ -87,48 +73,73 @@ open() {
 ////////////////////////////////////////////////////////////////////
 void UserDataAudio::
 read_samples(int n, PN_int16 *data) {
-  int nread = n;
-  if (nread > (int)_data.size()) {
-    nread = _data.size();
-  }
-  for (int i=0; i<nread; i++) {
+  int ready = (_data.size() / _desired_channels);
+  int desired = n * _desired_channels;
+  int avail = ready * _desired_channels;
+  if (avail > desired) avail = desired;
+  for (int i=0; i<avail; i++) {
     data[i] = _data[i];
   }
-  for (int i=nread; i<n; i++) {
+  for (int i=avail; i<desired; i++) {
     data[i] = 0;
   }
-  for (int i=0; i<nread; i++) {
+  for (int i=0; i<avail; i++) {
     _data.pop_front();
   }
-  update_cursor();
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: UserDataAudio::append
-//       Access: Published
+//       Access: Public
 //  Description: Appends audio samples to the buffer.
 ////////////////////////////////////////////////////////////////////
 void UserDataAudio::
-append(PN_int16 *data, int len) {
+append(PN_int16 *data, int n) {
   nassertv(!_aborted);
-  for (int i=0; i<len; i++) {
+  int words = n * _desired_channels;
+  for (int i=0; i<words; i++) {
     _data.push_back(data[i]);
   }
-  update_cursor();
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: UserDataAudio::append
 //       Access: Published
-//  Description: Appends audio samples to the buffer.
+//  Description: Appends audio samples to the buffer from a 
+//               datagram.  This is intended to make it easy to 
+//               send streaming raw audio over a network.
 ////////////////////////////////////////////////////////////////////
 void UserDataAudio::
-append(int val) {
+append(DatagramIterator *src, int n) {
   nassertv(!_aborted);
-  PN_int16 truncated = (PN_int16)val;
-  nassertv(truncated == val);
-  _data.push_back(truncated);
-  update_cursor();
+  int maxlen = src->get_remaining_size() / (2 * _desired_channels);
+  if (n > maxlen) n = maxlen;
+  int words = n * _desired_channels;
+  for (int i=0; i<words; i++) {
+    _data.push_back(src->get_int16());
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: UserDataAudio::append
+//       Access: Published
+//  Description: Appends audio samples to the buffer from a 
+//               string.  The samples must be stored little-endian
+//               in the string.  This is not particularly efficient,
+//               but it may be convenient to deal with samples in
+//               python.
+////////////////////////////////////////////////////////////////////
+void UserDataAudio::
+append(const string &str) {
+  nassertv(!_aborted);
+  int samples = str.size() / (2 * _desired_channels);
+  int words = samples * _desired_channels;
+  for (int i=0; i<words; i++) {
+    int c1 = ((unsigned char)str[i*2+0]);
+    int c2 = ((unsigned char)str[i*2+1]);
+    PN_int16 n = (c1 | (c2 << 8));
+    _data.push_back(n);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -140,5 +151,4 @@ append(int val) {
 void UserDataAudio::
 done() {
   _aborted = true;
-  update_cursor();
 }

+ 4 - 2
panda/src/movies/userDataAudio.h

@@ -20,6 +20,7 @@
 #define USERDATAAUDIO_H
 
 #include "movieAudio.h"
+#include "datagramIterator.h"
 class MovieAudioCursor;
 class UserDataAudioCursor;
 
@@ -35,8 +36,9 @@ class EXPCL_PANDA_MOVIES UserDataAudio : public MovieAudio {
   virtual ~UserDataAudio();
   virtual PT(MovieAudioCursor) open();
 
-  void append(PN_int16 *data, int len);
-  void append(int value); // Not fast enough, but useful for debugging.
+  void append(PN_int16 *data, int n);
+  void append(DatagramIterator *src, int len=0x40000000);
+  void append(const string &str);
   void done(); // A promise not to write any more samples.  
 
  private:

+ 14 - 1
panda/src/movies/userDataAudioCursor.cxx

@@ -33,7 +33,6 @@ UserDataAudioCursor(UserDataAudio *src) :
   _audio_channels = src->_desired_channels;
   _can_seek = false;
   _can_seek_fast = false;
-  _ready = 0;
   _aborted = false;
 }
 
@@ -60,4 +59,18 @@ void UserDataAudioCursor::
 read_samples(int n, PN_int16 *data) {
   UserDataAudio *source = (UserDataAudio*)(MovieAudio*)_source;
   source->read_samples(n, data);
+  _samples_read += n;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: UserDataAudioCursor::ready
+//       Access: Private
+//  Description: Returns the number of audio samples ready to be
+//               read.
+////////////////////////////////////////////////////////////////////
+int UserDataAudioCursor::
+ready() const {
+  UserDataAudio *source = (UserDataAudio*)(MovieAudio*)_source;
+  ((UserDataAudioCursor*)this)->_aborted = source->_aborted;
+  return (source->_data.size()) / _audio_channels;
 }

+ 1 - 0
panda/src/movies/userDataAudioCursor.h

@@ -38,6 +38,7 @@ PUBLISHED:
   
 public:
   virtual void read_samples(int n, PN_int16 *data);
+  virtual int ready() const;
   friend class UserDataAudio;
 
 public:

+ 2 - 8
panda/src/movies/webcamVideoDS.cxx

@@ -1,4 +1,4 @@
-// Filename: webcamVideoDX.cxx
+// Filename: webcamVideoDS.cxx
 // Created by: jyelon (01Nov2007)
 //
 ////////////////////////////////////////////////////////////////////
@@ -102,7 +102,7 @@ public:
     return _type_handle;
   }
   static void init_type() {
-    MovieVideo::init_type();
+    WebcamVideo::init_type();
     register_type(_type_handle, "WebcamVideoDS",
                   WebcamVideo::get_class_type());
   }
@@ -295,12 +295,6 @@ get_moniker_name(IMoniker *pMoniker) {
   return res;
 }
 
-struct DeviceInfo {
-  string _name;
-  IMoniker *_moniker;
-  AM_MEDIA_TYPE *_media;
-};
-
 ////////////////////////////////////////////////////////////////////
 //     Function: WebcamVideoDS::add_device
 //       Access: Public, Static