Panda3D
microphoneAudioDS.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file microphoneAudioDS.cxx
10  * @author jyelon
11  * @date 2007-11-01
12  *
13  * It goes against Panda3D coding style conventions to hide an
14  * entire class in a C++ file and not expose it through header
15  * files at all. However, in this case, these classes are so full
16  * of OS-specific junk that I feel it is better to hide them
17  * entirely. - Josh
18  */
19 
20 #ifdef HAVE_DIRECTCAM
21 
22 #ifndef WIN32_LEAN_AND_MEAN
23 #define WIN32_LEAN_AND_MEAN 1
24 #endif
25 
26 #undef Configure
27 
28 #include <windows.h>
29 #include <mmsystem.h>
30 
31 /**
32  * The directshow implementation of microphones.
33  */
34 class MicrophoneAudioDS : public MicrophoneAudio {
35 public:
36  static void find_all_microphones_ds();
37  friend void find_all_microphones_ds();
38 
39 private:
40  virtual PT(MovieAudioCursor) open();
41 
42  int _device_id;
43  int _manufacturer_id;
44  int _product_id;
45 
46  struct AudioBuf {
47  HGLOBAL _storage_gh;
48  HGLOBAL _header_gh;
49  LPSTR _storage;
50  LPWAVEHDR _header;
51  };
52  typedef pvector <AudioBuf> AudioBuffers;
53 
54  static void delete_buffers(AudioBuffers &buffers);
55 
56  friend class MicrophoneAudioCursorDS;
57 
58 public:
59  static TypeHandle get_class_type() {
60  return _type_handle;
61  }
62  static void init_type() {
63  MicrophoneAudio::init_type();
64  register_type(_type_handle, "MicrophoneAudioDS",
65  MicrophoneAudio::get_class_type());
66  }
67  virtual TypeHandle get_type() const {
68  return get_class_type();
69  }
70  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
71 
72 private:
73  static TypeHandle _type_handle;
74 };
75 
76 TypeHandle MicrophoneAudioDS::_type_handle;
77 
78 /**
79  * The directshow implementation of microphones.
80  */
81 
82 class MicrophoneAudioCursorDS : public MovieAudioCursor
83 {
84 public:
85  typedef MicrophoneAudioDS::AudioBuffers AudioBuffers;
86  MicrophoneAudioCursorDS(MicrophoneAudioDS *src, AudioBuffers &bufs, HWAVEIN hwav);
87  virtual ~MicrophoneAudioCursorDS();
88 
89  AudioBuffers _buffers;
90  HWAVEIN _hwavein;
91  int _samples_per_buffer;
92 
93 public:
94  virtual void read_samples(int n, int16_t *data);
95  virtual int ready() const;
96 
97 public:
98  void cleanup();
99 
100  HWAVEIN _handle;
101  int _next; // Which buffer is the next one to read from.
102  int _offset; // How many samples to skip in the buffer.
103 
104 public:
105  static TypeHandle get_class_type() {
106  return _type_handle;
107  }
108  static void init_type() {
109  MovieAudioCursor::init_type();
110  register_type(_type_handle, "MicrophoneAudioCursorDS",
111  MovieAudioCursor::get_class_type());
112  }
113  virtual TypeHandle get_type() const {
114  return get_class_type();
115  }
116  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
117 
118 private:
119  static TypeHandle _type_handle;
120 };
121 
122 TypeHandle MicrophoneAudioCursorDS::_type_handle;
123 
124 /**
125  * Finds all DirectShow microphones and adds them to the global list
126  * _all_microphones.
127  */
128 void MicrophoneAudioDS::
129 find_all_microphones_ds() {
130  MMRESULT stat;
131  static int freqs[] = { 11025, 22050, 44100, 48000, 0 };
132 
133  int ndevs = waveInGetNumDevs();
134  for (int i=0; i<ndevs; i++) {
135  WAVEINCAPS caps;
136  stat = waveInGetDevCaps(i, &caps, sizeof(caps));
137  if (stat != MMSYSERR_NOERROR) continue;
138  for (int chan=1; chan<=2; chan++) {
139  for (int fselect=0; freqs[fselect]; fselect++) {
140  WAVEFORMATEX format;
141  int freq = freqs[fselect];
142  format.wFormatTag = WAVE_FORMAT_PCM;
143  format.nChannels = chan;
144  format.nSamplesPerSec = freq;
145  format.nAvgBytesPerSec = freq * chan * 2;
146  format.nBlockAlign = 2 * chan;
147  format.wBitsPerSample = 16;
148  format.cbSize = 0;
149  stat = waveInOpen(nullptr, i, &format, 0, 0, WAVE_FORMAT_QUERY);
150  if (stat == MMSYSERR_NOERROR) {
151  PT(MicrophoneAudioDS) p = new MicrophoneAudioDS();
152  std::ostringstream name;
153  name << "WaveIn: " << caps.szPname << " Chan:" << chan << " HZ:" << freq;
154  p->set_name(name.str());
155  p->_device_id = i;
156  p->_manufacturer_id = caps.wMid;
157  p->_product_id = caps.wPid;
158  p->_rate = freq;
159  p->_channels = chan;
160  _all_microphones.push_back((MicrophoneAudioDS*)p);
161  }
162  }
163  }
164  }
165 }
166 
167 void find_all_microphones_ds() {
168  MicrophoneAudioDS::init_type();
169  // MicrophoneAudioCursorDS::init_type();
170  MicrophoneAudioDS::find_all_microphones_ds();
171 }
172 
173 /**
174  * Delete a set of audio buffers.
175  */
176 void MicrophoneAudioDS::
177 delete_buffers(AudioBuffers &buffers) {
178  for (int i=0; i<(int)buffers.size(); i++) {
179  AudioBuf &buf = buffers[i];
180  if (buf._header_gh) {
181  GlobalUnlock(buf._header_gh);
182  GlobalFree(buf._header_gh);
183  }
184  if (buf._storage_gh) {
185  GlobalUnlock(buf._storage_gh);
186  GlobalFree(buf._storage_gh);
187  }
188  }
189  buffers.clear();
190 }
191 
192 /**
193  * Open this video, returning a MovieVideoCursor.
194  */
195 PT(MovieAudioCursor) MicrophoneAudioDS::
196 open() {
197 
198  // Allocate the buffers. 64 buffers, not quite 120 sec each.
199  int samples;
200  switch (_rate) {
201  case 11025: samples=512; break;
202  case 22050: samples=1024; break;
203  case 44100: samples=2048; break;
204  }
205  int bytes = _channels * samples * 2;
206 
207  bool failed = false;
208  AudioBuffers buffers;
209  for (int i=0; i<64; i++) {
210  AudioBuf buf;
211  buf._storage_gh = 0;
212  buf._header_gh = 0;
213  buf._storage = 0;
214  buf._header = 0;
215 
216  buf._storage_gh = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, bytes);
217  buf._header_gh = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, sizeof(WAVEHDR));
218  if (buf._storage_gh != 0) {
219  buf._storage = (LPSTR)GlobalLock(buf._storage_gh);
220  }
221  if (buf._header_gh != 0) {
222  buf._header = (LPWAVEHDR)GlobalLock(buf._header_gh);
223  }
224  if (buf._storage && buf._header) {
225  ZeroMemory(buf._header, sizeof(WAVEHDR));
226  buf._header->lpData = buf._storage;
227  buf._header->dwBufferLength = bytes;
228  } else {
229  failed = true;
230  }
231  buffers.push_back(buf);
232  if (failed) break;
233  }
234 
235  if (failed) {
236  delete_buffers(buffers);
237  nassert_raise("Could not allocate audio input buffers.");
238  return nullptr;
239  }
240 
241  WAVEFORMATEX format;
242  format.wFormatTag = WAVE_FORMAT_PCM;
243  format.nChannels = _channels;
244  format.nSamplesPerSec = _rate;
245  format.nAvgBytesPerSec = _rate * _channels * 2;
246  format.nBlockAlign = 2 * _channels;
247  format.wBitsPerSample = 16;
248  format.cbSize = 0;
249 
250  HWAVEIN hwav;
251  MMRESULT stat = waveInOpen(&hwav, _device_id, &format, 0, 0, CALLBACK_NULL);
252 
253  if (stat != MMSYSERR_NOERROR) {
254  delete_buffers(buffers);
255  nassert_raise("Could not open audio input device.");
256  return nullptr;
257  }
258 
259  for (int i=0; i<(int)buffers.size(); i++) {
260  stat = waveInPrepareHeader(hwav, buffers[i]._header, sizeof(WAVEHDR));
261  if (stat == MMSYSERR_NOERROR) {
262  stat = waveInAddBuffer(hwav, buffers[i]._header, sizeof(WAVEHDR));
263  }
264  if (stat != MMSYSERR_NOERROR) {
265  waveInClose(hwav);
266  delete_buffers(buffers);
267  nassert_raise("Could not queue buffers for audio input device.");
268  return nullptr;
269  }
270  }
271  stat = waveInStart(hwav);
272  if (stat != MMSYSERR_NOERROR) {
273  waveInClose(hwav);
274  delete_buffers(buffers);
275  nassert_raise("Could not start recording on input device.");
276  return nullptr;
277  }
278  return new MicrophoneAudioCursorDS(this, buffers, hwav);
279 }
280 
281 /**
282  *
283  */
284 MicrophoneAudioCursorDS::
285 MicrophoneAudioCursorDS(MicrophoneAudioDS *src, AudioBuffers &bufs, HWAVEIN hwav) :
286  MovieAudioCursor(src),
287  _buffers(bufs),
288  _handle(hwav),
289  _next(0),
290  _offset(0)
291 {
292  _audio_rate = src->get_rate();
293  _audio_channels = src->get_channels();
294  _length = 1.0E10;
295  _can_seek = false;
296  _can_seek_fast = false;
297  _aborted = false;
298  _samples_per_buffer = bufs[0]._header->dwBufferLength / (2 * _audio_channels);
299 }
300 
301 /**
302  *
303  */
304 void MicrophoneAudioCursorDS::
305 cleanup() {
306  if (_handle) {
307  waveInClose(_handle);
308  _handle = 0;
309  }
310  MicrophoneAudioDS::delete_buffers(_buffers);
311  _next = 0;
312  _offset = 0;
313 }
314 
315 /**
316  *
317  */
318 MicrophoneAudioCursorDS::
319 ~MicrophoneAudioCursorDS() {
320  cleanup();
321 }
322 
323 /**
324  *
325  */
326 void MicrophoneAudioCursorDS::
327 read_samples(int n, int16_t *data) {
328  int orign = n;
329  if (_handle) {
330  while (1) {
331  int index = _next % _buffers.size();
332  if ((_buffers[index]._header->dwFlags & WHDR_DONE)==0) {
333  break;
334  }
335 
336  // Find start of data in buffer.
337  int16_t *src = (int16_t*)(_buffers[index]._storage);
338  src += (_offset * _audio_channels);
339 
340  // Decide how many samples to extract from this buffer.
341  int samples = _samples_per_buffer;
342  samples -= _offset;
343  if (samples > n) samples = n;
344 
345  // Copy data to output buffer.
346  memcpy(data, src, samples * 2 * _audio_channels);
347 
348  // Advance pointers.
349  data += samples * _audio_channels;
350  n -= samples;
351  _offset += samples;
352  _samples_read += samples;
353  if (_offset != _samples_per_buffer) {
354  break;
355  }
356  _buffers[index]._header->dwFlags &= ~(WHDR_DONE);
357  MMRESULT stat = waveInUnprepareHeader(_handle, _buffers[index]._header, sizeof(WAVEHDR));
358  if (stat == MMSYSERR_NOERROR) {
359  stat = waveInPrepareHeader(_handle, _buffers[index]._header, sizeof(WAVEHDR));
360  }
361  if (stat == MMSYSERR_NOERROR) {
362  stat = waveInAddBuffer(_handle, _buffers[index]._header, sizeof(WAVEHDR));
363  }
364  if (stat != MMSYSERR_NOERROR) {
365  movies_cat.error() << "Could not requeue audio buffers, closing microphone.\n";
366  cleanup();
367  break;
368  }
369  _next += 1;
370  _offset = 0;
371  }
372  }
373  if (n > 0) {
374  memcpy(data, 0, n*2*_audio_channels);
375  }
376 }
377 
378 /**
379  *
380  */
381 int MicrophoneAudioCursorDS::
382 ready() const {
383  if (_handle == 0) return 0;
384  int total = 0;
385  for (int i=0; i<(int)_buffers.size(); i++) {
386  int index = (_next + i) % (_buffers.size());
387  if ((_buffers[index]._header->dwFlags & WHDR_DONE)==0) {
388  break;
389  }
390  total += _samples_per_buffer;
391  }
392  total -= _offset;
393  return total;
394 }
395 
396 
397 #endif // HAVE_DIRECTSHOW
void read_samples(int n, Datagram *dg)
Read audio samples from the stream into a Datagram.
void register_type(TypeHandle &type_handle, const std::string &name)
This inline function is just a convenient way to call TypeRegistry::register_type(),...
Definition: register_type.I:22
Class MicrophoneAudio provides the means to read raw audio samples from a microphone.
This is our own Panda specialization on the default STL vector.
Definition: pvector.h:42
A MovieAudio is actually any source that provides a sequence of audio samples.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
virtual int ready() const
Returns the number of audio samples that are ready to read.