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
vorbisAudio.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
TypeHandle
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
VirtualFileSystem
A hierarchy of directories and files that appears to be one continuous file system,...
Definition: virtualFileSystem.h:40
vorbisAudioCursor.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
config_movies.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
MovieAudioCursor
A MovieAudio is actually any source that provides a sequence of audio samples.
Definition: movieAudioCursor.h:34
VirtualFileSystem::get_global_ptr
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
Definition: virtualFileSystem.cxx:741
virtualFileSystem.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
VirtualFileSystem::close_read_file
static void close_read_file(std::istream *stream)
Closes a file opened by a previous call to open_read_file().
Definition: virtualFileSystem.cxx:866