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
293 VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
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
MovieAudioCursor
A MovieAudio is actually any source that provides a sequence of audio samples.
Definition: movieAudioCursor.h:34
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
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
VirtualFileSystem::get_global_ptr
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
Definition: virtualFileSystem.cxx:741
config_movies.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtualFileSystem.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
vorbisAudioCursor.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
vorbisAudio.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.