Panda3D
Loading...
Searching...
No Matches
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.
20static 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
55static 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
90TypeHandle 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 */
97WavAudioCursor(WavAudio *src, std::istream *stream) :
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 */
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 */
293seek(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 */
362read_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:44
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.
uint8_t get_uint8()
Extracts an unsigned 8-bit integer.
uint16_t get_uint16()
Extracts an unsigned 16-bit integer.
int16_t get_int16()
Extracts a signed 16-bit integer.
float get_float32()
Extracts a 32-bit single-precision floating-point number.
PN_float64 get_float64()
Extracts a 64-bit floating-point number.
int32_t get_int32()
Extracts a signed 32-bit integer.
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.
uint32_t get_uint32()
Extracts an unsigned 32-bit integer.
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.