Panda3D
vorbisAudioCursor.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 vorbisAudioCursor.cxx
10  * @author rdb
11  * @date 2013-08-23
12  */
13 
14 #include "vorbisAudioCursor.h"
15 
16 #include "config_movies.h"
17 
18 #include "vorbisAudio.h"
19 #include "virtualFileSystem.h"
20 
21 #ifdef HAVE_VORBIS
22 
23 using std::istream;
24 
25 TypeHandle VorbisAudioCursor::_type_handle;
26 
27 /**
28  * Reads the .wav header from the indicated stream. This leaves the read
29  * pointer positioned at the start of the data.
30  */
31 VorbisAudioCursor::
32 VorbisAudioCursor(VorbisAudio *src, istream *stream) :
33  MovieAudioCursor(src),
34  _is_valid(false),
35  _bitstream(0)
36 {
37  nassertv(stream != nullptr);
38  nassertv(stream->good());
39 
40  // Set up the callbacks to read via the VFS.
41  ov_callbacks callbacks;
42  callbacks.read_func = &cb_read_func;
43  callbacks.close_func = &cb_close_func;
44  callbacks.tell_func = &cb_tell_func;
45 
46  if (vorbis_enable_seek) {
47  callbacks.seek_func = &cb_seek_func;
48  } else {
49  callbacks.seek_func = nullptr;
50  }
51 
52  if (ov_open_callbacks((void*) stream, &_ov, nullptr, 0, callbacks) != 0) {
53  movies_cat.error()
54  << "Failed to read Ogg Vorbis file.\n";
55  return;
56  }
57 
58  double time_total = ov_time_total(&_ov, -1);
59  if (time_total != OV_EINVAL) {
60  _length = time_total;
61  }
62 
63  vorbis_info *vi = ov_info(&_ov, -1);
64  _audio_channels = vi->channels;
65  _audio_rate = vi->rate;
66 
67  _can_seek = vorbis_enable_seek && (ov_seekable(&_ov) != 0);
68  _can_seek_fast = _can_seek;
69 
70  _is_valid = true;
71 }
72 
73 /**
74  * xxx
75  */
76 VorbisAudioCursor::
77 ~VorbisAudioCursor() {
78  ov_clear(&_ov);
79 }
80 
81 /**
82  * Seeks to a target location. Afterward, the packet_time is guaranteed to be
83  * less than or equal to the specified time.
84  */
85 void VorbisAudioCursor::
86 seek(double t) {
87  if (!vorbis_enable_seek) {
88  return;
89  }
90 
91  t = std::max(t, 0.0);
92 
93  // Use ov_time_seek_lap if cross-lapping is enabled.
94  int result;
95  if (vorbis_seek_lap) {
96  result = ov_time_seek_lap(&_ov, t);
97  } else {
98  result = ov_time_seek(&_ov, t);
99  }
100 
101  // Special case for seeking to the beginning; if normal seek fails, we may
102  // be able to explicitly seek to the beginning of the file and call ov_open
103  // again. This allows looping compressed .ogg files.
104  if (result == OV_ENOSEEK && t == 0.0) {
105  std::istream *stream = (std::istream *)_ov.datasource;
106 
107  if (stream->rdbuf()->pubseekpos(0, std::ios::in) == (std::streampos)0) {
108  // Back up the callbacks, then destroy the stream, making sure to first
109  // unset the datasource so that it won't close the file.
110  ov_callbacks callbacks = _ov.callbacks;
111  _ov.datasource = nullptr;
112  ov_clear(&_ov);
113 
114  if (ov_open_callbacks((void *)stream, &_ov, nullptr, 0, callbacks) != 0) {
115  movies_cat.error()
116  << "Failed to reopen Ogg Vorbis file to seek to beginning.\n";
117  return;
118  }
119 
120  // Reset these fields for good measure, just in case the file changed.
121  vorbis_info *vi = ov_info(&_ov, -1);
122  _audio_channels = vi->channels;
123  _audio_rate = vi->rate;
124 
125  _last_seek = 0.0;
126  _samples_read = 0;
127  return;
128  }
129  }
130  if (result != 0) {
131  movies_cat.error()
132  << "Seek failed. Ogg Vorbis stream may not be seekable.\n";
133  }
134 
135  _last_seek = ov_time_tell(&_ov);
136  _samples_read = 0;
137 }
138 
139 /**
140  * Read audio samples from the stream. N is the number of samples you wish to
141  * read. Your buffer must be equal in size to N * channels. Multiple-channel
142  * audio will be interleaved.
143  */
144 void VorbisAudioCursor::
145 read_samples(int n, int16_t *data) {
146  int desired = n * _audio_channels;
147 
148  char *buffer = (char*) data;
149  int length = desired * 2;
150 
151  // Call ov_read repeatedly until the buffer is full.
152  while (length > 0) {
153  int bitstream;
154 
155  // ov_read can give it to us in the exact format we need. Nifty!
156  long read_bytes = ov_read(&_ov, buffer, length, 0, 2, 1, &bitstream);
157  if (read_bytes > 0) {
158  buffer += read_bytes;
159  length -= read_bytes;
160  } else {
161  break;
162  }
163 
164  if (_bitstream != bitstream) {
165  // It is technically possible for it to change parameters from one
166  // bitstream to the next. However, we don't offer this flexibility.
167  vorbis_info *vi = ov_info(&_ov, -1);
168  if (vi->channels != _audio_channels || vi->rate != _audio_rate) {
169  movies_cat.error()
170  << "Ogg Vorbis file has non-matching bitstreams!\n";
171 
172  // We'll change it anyway. Not sure what happens next.
173  _audio_channels = vi->channels;
174  _audio_rate = vi->rate;
175  break;
176  }
177 
178  _bitstream = bitstream;
179  }
180  }
181 
182  // Fill the rest of the buffer with silence.
183  if (length > 0) {
184  memset(buffer, 0, length);
185  n -= length / 2 / _audio_channels;
186  }
187 
188  _samples_read += n;
189 }
190 
191 /**
192  * Callback passed to libvorbisfile to implement file I/O via the
193  * VirtualFileSystem.
194  */
195 size_t VorbisAudioCursor::
196 cb_read_func(void *ptr, size_t size, size_t nmemb, void *datasource) {
197  istream *stream = (istream*) datasource;
198  nassertr(stream != nullptr, -1);
199 
200  stream->read((char *)ptr, size * nmemb);
201 
202  if (stream->eof()) {
203  // Gracefully handle EOF.
204  stream->clear();
205  }
206 
207  return stream->gcount();
208 }
209 
210 /**
211  * Callback passed to libvorbisfile to implement file I/O via the
212  * VirtualFileSystem.
213  */
214 int VorbisAudioCursor::
215 cb_seek_func(void *datasource, ogg_int64_t offset, int whence) {
216  if (!vorbis_enable_seek) {
217  return -1;
218  }
219 
220  istream *stream = (istream*) datasource;
221  nassertr(stream != nullptr, -1);
222 
223  switch (whence) {
224  case SEEK_SET:
225  stream->seekg(offset, std::ios::beg);
226  break;
227 
228  case SEEK_CUR:
229  // Vorbis uses a seek with offset 0 to determine whether seeking is
230  // supported, but this is not good enough. We seek to the end and back.
231  if (offset == 0) {
232  std::streambuf *buf = stream->rdbuf();
233  std::streampos pos = buf->pubseekoff(0, std::ios::cur, std::ios::in);
234  if (pos < 0) {
235  return -1;
236  }
237  if (buf->pubseekoff(0, std::ios::end, std::ios::in) >= 0) {
238  // It worked; seek back to the previous location.
239  buf->pubseekpos(pos, std::ios::in);
240  return 0;
241  } else {
242  return -1;
243  }
244  }
245  stream->seekg(offset, std::ios::cur);
246  break;
247 
248  case SEEK_END:
249  stream->seekg(offset, std::ios::end);
250  break;
251 
252  default:
253  movies_cat.error()
254  << "Illegal parameter to seek in VorbisAudioCursor::cb_seek_func\n";
255  return -1;
256  }
257 
258  if (stream->fail()) {
259  // This is a fatal error and usually leads to a libvorbis crash.
260  movies_cat.error()
261  << "Failure to seek to byte " << offset;
262 
263  switch (whence) {
264  case SEEK_CUR:
265  movies_cat.error(false)
266  << " from current location!\n";
267  break;
268 
269  case SEEK_END:
270  movies_cat.error(false)
271  << " from end of file!\n";
272  break;
273 
274  default:
275  movies_cat.error(false) << "!\n";
276  }
277 
278  return -1;
279  }
280 
281  return 0;
282 }
283 
284 /**
285  * Callback passed to libvorbisfile to implement file I/O via the
286  * VirtualFileSystem.
287  */
288 int VorbisAudioCursor::
289 cb_close_func(void *datasource) {
290  istream *stream = (istream*) datasource;
291  nassertr(stream != nullptr, -1);
292 
294  vfs->close_read_file(stream);
295 
296  // Return value isn't checked, but let's be predictable
297  return 0;
298 }
299 
300 /**
301  * Callback passed to libvorbisfile to implement file I/O via the
302  * VirtualFileSystem.
303  */
304 long VorbisAudioCursor::
305 cb_tell_func(void *datasource) {
306  istream *stream = (istream*) datasource;
307  nassertr(stream != nullptr, -1);
308 
309  return stream->tellg();
310 }
311 
312 #endif // HAVE_VORBIS
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
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().
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
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.