Panda3D
wavAudioCursor.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 wavAudioCursor.cxx
10  * @author rdb
11  * @date 2013-08-23
12  */
13 
14 #include "wavAudioCursor.h"
15 #include "config_movies.h"
16 #include "virtualFileSystem.h"
17 #include "wavAudio.h"
18 
19 // Tables for decompressing mu-law and A-law wav files.
20 static int16_t mulaw_table[256] = {
21  -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956,
22  -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764,
23  -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412,
24  -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316,
25  -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
26  -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
27  -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
28  -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
29  -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
30  -1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
31  -876, -844, -812, -780, -748, -716, -684, -652,
32  -620, -588, -556, -524, -492, -460, -428, -396,
33  -372, -356, -340, -324, -308, -292, -276, -260,
34  -244, -228, -212, -196, -180, -164, -148, -132,
35  -120, -112, -104, -96, -88, -80, -72, -64,
36  -56, -48, -40, -32, -24, -16, -8, -1,
37  32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
38  23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
39  15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
40  11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316,
41  7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140,
42  5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092,
43  3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004,
44  2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980,
45  1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436,
46  1372, 1308, 1244, 1180, 1116, 1052, 988, 924,
47  876, 844, 812, 780, 748, 716, 684, 652,
48  620, 588, 556, 524, 492, 460, 428, 396,
49  372, 356, 340, 324, 308, 292, 276, 260,
50  244, 228, 212, 196, 180, 164, 148, 132,
51  120, 112, 104, 96, 88, 80, 72, 64,
52  56, 48, 40, 32, 24, 16, 8, 0
53 };
54 
55 static int16_t alaw_table[256] = {
56  -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
57  -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
58  -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
59  -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
60  -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944,
61  -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136,
62  -11008,-10496,-12032,-11520,-8960, -8448, -9984, -9472,
63  -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568,
64  -344, -328, -376, -360, -280, -264, -312, -296,
65  -472, -456, -504, -488, -408, -392, -440, -424,
66  -88, -72, -120, -104, -24, -8, -56, -40,
67  -216, -200, -248, -232, -152, -136, -184, -168,
68  -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
69  -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
70  -688, -656, -752, -720, -560, -528, -624, -592,
71  -944, -912, -1008, -976, -816, -784, -880, -848,
72  5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736,
73  7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784,
74  2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368,
75  3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392,
76  22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944,
77  30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136,
78  11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472,
79  15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568,
80  344, 328, 376, 360, 280, 264, 312, 296,
81  472, 456, 504, 488, 408, 392, 440, 424,
82  88, 72, 120, 104, 24, 8, 56, 40,
83  216, 200, 248, 232, 152, 136, 184, 168,
84  1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184,
85  1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696,
86  688, 656, 752, 720, 560, 528, 624, 592,
87  944, 912, 1008, 976, 816, 784, 880, 848
88 };
89 
90 TypeHandle WavAudioCursor::_type_handle;
91 
92 /**
93  * Reads the .wav header from the indicated stream. This leaves the read
94  * pointer positioned at the start of the data.
95  */
97 WavAudioCursor(WavAudio *src, std::istream *stream) :
98  MovieAudioCursor(src),
99  _is_valid(false),
100  _stream(stream),
101  _reader(stream, false),
102  _format(F_pcm),
103  _data_pos(0),
104  _data_size(0)
105 {
106  nassertv(stream != nullptr);
107 
108  // Beginning of "RIFF" chunk.
109  unsigned char magic[4];
110  if (_reader.extract_bytes(magic, 4) != 4 || memcmp(magic, "RIFF", 4) != 0) {
111  movies_cat.error()
112  << ".wav file is not a valid RIFF file.\n";
113  return;
114  }
115 
116  unsigned int chunk_size = _reader.get_uint32();
117 
118  if (_reader.extract_bytes(magic, 4) != 4 || memcmp(magic, "WAVE", 4) != 0) {
119  movies_cat.error()
120  << ".wav file is a RIFF file but does not start with a WAVE chunk.\n";
121  return;
122  }
123 
124  // Find a "fmt " subchunk followed by a "data" subchunk.
125  bool have_fmt = false, have_data = false;
126  unsigned int bytes_read = 4;
127 
128  while ((!have_fmt || !have_data) && _stream->good() && (bytes_read + 8) < chunk_size) {
129 
130  _reader.extract_bytes(magic, 4);
131  unsigned int subchunk_size = _reader.get_uint32();
132 
133  if (memcmp(magic, "fmt ", 4) == 0) {
134  // The format chunk specifies information about the storage.
135  nassertv(subchunk_size >= 16);
136  have_fmt = true;
137 
138  _format = (Format) _reader.get_uint16();
139 
140  _audio_channels = _reader.get_uint16();
141  _audio_rate = _reader.get_uint32();
142 
143  _byte_rate = (double) _reader.get_uint32();
144  _block_align = _reader.get_uint16();
145 
146  // We can round up to next multiple of 8.
147  uint16_t bps = _reader.get_uint16();
148  bps = (bps + 7) & 0xfff8;
149 
150  // How many bytes in this chunk we've read so far.
151  unsigned int read_bytes = 16;
152 
153  // See if there is an extra header to read.
154  if (_format == F_extensible) {
155  unsigned short ext_size = _reader.get_uint16();
156  read_bytes += 2;
157 
158  if (ext_size >= 8) {
159  /*n_valid_bits =*/ _reader.get_uint16();
160  /*speaker_mask =*/ _reader.get_uint32();
161  _format = (Format) _reader.get_uint16();
162 
163  read_bytes += 8;
164  }
165  }
166 
167  switch (_format) {
168  case F_pcm:
169  if (bps != 8 && bps != 16 && bps != 24 && bps != 32 && bps != 64) {
170  movies_cat.error()
171  << "Unsupported number of bits per sample for PCM storage: " << bps << "\n";
172  return;
173  }
174  break;
175 
176  case F_float:
177  if (bps != 32 && bps != 64) {
178  movies_cat.error()
179  << "Unsupported number of bits per sample for float storage: " << bps << "\n";
180  return;
181  }
182  break;
183 
184  case F_alaw:
185  case F_mulaw:
186  if (bps != 8) {
187  movies_cat.error()
188  << ".wav file with A-law or mu-law compression must specify 8 bits per sample, not " << bps << ".\n";
189  return;
190  }
191  break;
192 
193  default:
194  movies_cat.error()
195  << "Unsupported .wav format " << _format << ". Only PCM, float, A-law and mu-law encodings are supported.\n";
196  return;
197  }
198 
199  _bytes_per_sample = bps / 8;
200 
201  // Skip to the end of the chunk.
202  if (subchunk_size > read_bytes) {
203  _reader.skip_bytes(subchunk_size - read_bytes);
204  }
205 
206  } else if (memcmp(magic, "data", 4) == 0) {
207  // The data chunk contains the actual sammples.
208  if (!have_fmt) {
209  movies_cat.error()
210  << ".wav file specifies 'data' chunk before 'fmt ' chunk.\n";
211  break;
212  }
213 
214  // Excellent! We've reached the beginning of the data. Write down
215  // where we are and don't move until we want to start reading the data.
216  _data_start = stream->tellg();
217  _data_size = subchunk_size;
218  have_data = true;
219  break;
220 
221  } else {
222  // A chunk we do not recognize. Just skip it.
223  _reader.skip_bytes(subchunk_size);
224  }
225 
226  bytes_read += subchunk_size + 8;
227  }
228 
229  // If we bailed out prematurely, there must have been an error.
230  if (_stream->eof()) {
231  movies_cat.error()
232  << "Reached end of file while reading .wav file header.\n";
233 
234  } else if (!_stream->good()) {
235  movies_cat.error()
236  << "Stream error while reading .wav file header.\n";
237  return;
238  }
239 
240  if (!have_fmt) {
241  movies_cat.error()
242  << ".wav file did not specify a 'fmt ' chunk.\n";
243  return;
244  }
245 
246  if (!have_data) {
247  movies_cat.error()
248  << ".wav file did not specify a 'data' chunk.\n";
249  return;
250  }
251 
252  // We can always seek by skipping bytes, rereading if necessary.
253  _can_seek = true;
254 
255  // How to tell if a stream is seekable? We'll set it to true, and then
256  // change it to false as soon as we find out that we can't.
257  _can_seek_fast = true;
258 
259  if (_block_align != _audio_channels * _bytes_per_sample) {
260  movies_cat.warning()
261  << ".wav file specified an unexpected block alignment of " << _block_align
262  << ", expected " << (_audio_channels * _bytes_per_sample) << ".\n";
263  _block_align = _audio_channels * _bytes_per_sample;
264  }
265 
266  if (_byte_rate != _audio_rate * _block_align) {
267  movies_cat.warning()
268  << ".wav file specified an unexpected byte rate of " << _byte_rate
269  << ", expected " << (_audio_rate * _block_align) << ".\n";
270  _byte_rate = _audio_rate * _block_align;
271  }
272 
273  _length = _data_size / _byte_rate;
274  _is_valid = true;
275 }
276 
277 /**
278  * xxx
279  */
281 ~WavAudioCursor() {
282  if (_stream != nullptr) {
284  vfs->close_read_file(_stream);
285  }
286 }
287 
288 /**
289  * Seeks to a target location. Afterward, the packet_time is guaranteed to be
290  * less than or equal to the specified time.
291  */
293 seek(double t) {
294  t = std::max(t, 0.0);
295  std::streampos pos = _data_start + (std::streampos) std::min((size_t) (t * _byte_rate), _data_size);
296 
297  std::streambuf *buf = _stream->rdbuf();
298 
299  if (_can_seek_fast) {
300  if (buf->pubseekpos(pos, std::ios::in) != pos) {
301  // Clearly, we can't seek fast. Fall back to the case below.
302  _can_seek_fast = false;
303  }
304  }
305 
306  // Get the current position of the cursor in the file.
307  std::streampos current = buf->pubseekoff(0, std::ios::cur, std::ios::in);
308 
309  if (!_can_seek_fast) {
310  if (pos > current) {
311  // It is ahead of our current position. Skip ahead.
312  _stream->ignore(pos - current);
313  current = pos;
314 
315  } else if (pos < current) {
316  // Can we seek to the beginning? Some streams, such as ZStream, let us
317  // rewind the stream.
318  if (buf->pubseekpos(0, std::ios::in) == (std::streampos)0) {
319  if (pos > _data_start && movies_cat.is_info()) {
320  Filename fn = get_source()->get_filename();
321  movies_cat.info()
322  << "Unable to seek backwards in " << fn.get_basename()
323  << "; seeking to beginning and skipping " << pos << " bytes.\n";
324  }
325  _stream->ignore(pos);
326  current = pos;
327  } else {
328  // No; close and reopen the file.
329  Filename fn = get_source()->get_filename();
330  movies_cat.warning()
331  << "Unable to seek backwards in " << fn.get_basename()
332  << "; reopening and skipping " << pos << " bytes.\n";
333 
335  std::istream *stream = vfs->open_read_file(get_source()->get_filename(), true);
336  if (stream != nullptr) {
337  vfs->close_read_file(_stream);
338  stream->ignore(pos);
339  _stream = stream;
340  _reader = StreamReader(stream, false);
341  current = pos;
342  } else {
343  movies_cat.error()
344  << "Unable to reopen " << fn << ".\n";
345  _can_seek = false;
346  }
347  }
348  }
349  }
350 
351  _data_pos = (size_t)current - _data_start;
352  _last_seek = _data_pos / _byte_rate;
353  _samples_read = 0;
354 }
355 
356 /**
357  * Read audio samples from the stream. N is the number of samples you wish to
358  * read. Your buffer must be equal in size to N * channels. Multiple-channel
359  * audio will be interleaved.
360  */
362 read_samples(int n, int16_t *data) {
363  int desired = n * _audio_channels;
364  int read_samples = std::min(desired, ((int) (_data_size - _data_pos)) / _bytes_per_sample);
365 
366  if (read_samples <= 0) {
367  return;
368  }
369 
370  switch (_format) {
371  case F_pcm:
372  // Linear PCM storage.
373  switch (_bytes_per_sample) {
374  case 1:
375  // Upsample. Also, 8-bits samples are stored unsigned.
376  for (int i = 0; i < read_samples; ++i) {
377  data[i] = (_reader.get_uint8() - 128) * 258/*ish*/;
378  }
379  break;
380 
381  case 2:
382  // Our native format. Read directly from file.
383  read_samples = _reader.extract_bytes((unsigned char*) data, read_samples * 2) / 2;
384  break;
385 
386  case 3: {
387  // The scale factor happens to be 256 for 24-bit samples. That means we
388  // can just read the most significant bytes.
389  for (int i = 0; i < read_samples; ++i) {
390  _reader.skip_bytes(1);
391  data[i] = _reader.get_int16();
392  }
393  break;
394 
395  } case 4: {
396  // Downsample.
397  const int32_t scale_factor = 0x7fffffff / 0x7fff;
398 
399  for (int i = 0; i < read_samples; ++i) {
400  data[i] = _reader.get_int32() / scale_factor;
401  }
402  break;
403 
404  } case 8: {
405  // Downsample.
406  const int64_t scale_factor = 0x7fffffffffffffffLL / 0x7fffLL;
407 
408  for (int i = 0; i < read_samples; ++i) {
409  data[i] = _reader.get_int64() / scale_factor;
410  }
411  break;
412 
413  } default:
414  // Huh?
415  read_samples = 0;
416  }
417  break;
418 
419  case F_float:
420  // IEEE float storage.
421  switch (_bytes_per_sample) {
422  case 4:
423  for (int i = 0; i < read_samples; ++i) {
424  data[i] = (int16_t) (_reader.get_float32() * 0x7fff);
425  }
426  break;
427 
428  case 8:
429  for (int i = 0; i < read_samples; ++i) {
430  data[i] = (int16_t) (_reader.get_float64() * 0x7fff);
431  }
432  break;
433 
434  default:
435  read_samples = 0;
436  }
437  break;
438 
439  case F_alaw:
440  for (int i = 0; i < read_samples; ++i) {
441  data[i] = alaw_table[_reader.get_uint8()];
442  }
443  break;
444 
445  case F_mulaw:
446  for (int i = 0; i < read_samples; ++i) {
447  data[i] = mulaw_table[_reader.get_uint8()];
448  }
449  break;
450 
451  default:
452  read_samples = 0;
453  }
454 
455  // Fill the rest of the buffer with silence.
456  if (read_samples < desired) {
457  memset(data + read_samples, 0, (desired - read_samples) * 2);
458  }
459 
460  _data_pos = _stream->tellg() - _data_start;
461  _samples_read += read_samples / _audio_channels;
462 }
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
std::string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:367
A MovieAudio is actually any source that provides a sequence of audio samples.
A class to read sequential binary data directly from an istream.
Definition: streamReader.h:28
uint8_t get_uint8()
Extracts an unsigned 8-bit integer.
Definition: streamReader.I:95
uint16_t get_uint16()
Extracts an unsigned 16-bit integer.
Definition: streamReader.I:139
int16_t get_int16()
Extracts a signed 16-bit integer.
Definition: streamReader.I:103
float get_float32()
Extracts a 32-bit single-precision floating-point number.
Definition: streamReader.I:177
PN_float64 get_float64()
Extracts a 64-bit floating-point number.
Definition: streamReader.I:194
int32_t get_int32()
Extracts a signed 32-bit integer.
Definition: streamReader.I:115
void skip_bytes(size_t size)
Skips over the indicated number of bytes in the stream.
size_t extract_bytes(unsigned char *into, size_t size)
Extracts the indicated number of bytes in the stream into the given character buffer.
int64_t get_int64()
Extracts a signed 64-bit integer.
Definition: streamReader.I:127
uint32_t get_uint32()
Extracts an unsigned 32-bit integer.
Definition: streamReader.I:151
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
A hierarchy of directories and files that appears to be one continuous file system,...
static void close_read_file(std::istream *stream)
Closes a file opened by a previous call to open_read_file().
std::istream * open_read_file(const Filename &filename, bool auto_unwrap) const
Convenience function; returns a newly allocated istream if the file exists and can be read,...
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
WavAudioCursor(WavAudio *src, std::istream *stream)
Reads the .wav header from the indicated stream.
virtual void seek(double offset)
Seeks to a target location.
virtual void read_samples(int n, int16_t *data)
Read audio samples from the stream.
virtual ~WavAudioCursor()
xxx
A native PCM .wav loader.
Definition: wavAudio.h:26
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.