Panda3D
Loading...
Searching...
No Matches
opusAudioCursor.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 opusAudioCursor.cxx
10 * @author rdb
11 * @date 2017-05-24
12 */
13
14#include "opusAudioCursor.h"
15
16#include "config_movies.h"
17
18#include "opusAudio.h"
19#include "virtualFileSystem.h"
20
21#ifdef HAVE_OPUS
22
23#include <opus/opusfile.h>
24
25using std::istream;
26
27/**
28 * Callbacks passed to libopusfile to implement file I/O via the
29 * VirtualFileSystem.
30 */
31int cb_read(void *stream, unsigned char *ptr, int nbytes) {
32 istream *in = (istream *)stream;
33 nassertr(in != nullptr, -1);
34
35 in->read((char *)ptr, nbytes);
36
37 if (in->eof()) {
38 // Gracefully handle EOF.
39 in->clear();
40 }
41
42 return in->gcount();
43}
44
45int cb_seek(void *stream, opus_int64 offset, int whence) {
46 if (!opus_enable_seek) {
47 return -1;
48 }
49
50 istream *in = (istream *)stream;
51 nassertr(in != nullptr, -1);
52
53 switch (whence) {
54 case SEEK_SET:
55 in->seekg(offset, std::ios::beg);
56 break;
57
58 case SEEK_CUR:
59 // opusfile uses a seek with offset 0 to determine whether seeking is
60 // supported, but this is not good enough. We seek to the end and back.
61 if (offset == 0) {
62 std::streambuf *buf = in->rdbuf();
63 std::streampos pos = buf->pubseekoff(0, std::ios::cur, std::ios::in);
64 if (pos < 0) {
65 return -1;
66 }
67 if (buf->pubseekoff(0, std::ios::end, std::ios::in) >= 0) {
68 // It worked; seek back to the previous location.
69 buf->pubseekpos(pos, std::ios::in);
70 return 0;
71 } else {
72 return -1;
73 }
74 }
75 in->seekg(offset, std::ios::cur);
76 break;
77
78 case SEEK_END:
79 in->seekg(offset, std::ios::end);
80 break;
81
82 default:
83 movies_cat.error()
84 << "Illegal parameter to seek in cb_seek\n";
85 return -1;
86 }
87
88 if (in->fail()) {
89 movies_cat.error()
90 << "Failure to seek to byte " << offset;
91
92 switch (whence) {
93 case SEEK_CUR:
94 movies_cat.error(false)
95 << " from current location!\n";
96 break;
97
98 case SEEK_END:
99 movies_cat.error(false)
100 << " from end of file!\n";
101 break;
102
103 default:
104 movies_cat.error(false) << "!\n";
105 }
106
107 return -1;
108 }
109
110 return 0;
111}
112
113opus_int64 cb_tell(void *stream) {
114 istream *in = (istream *)stream;
115 nassertr(in != nullptr, -1);
116
117 return in->tellg();
118}
119
120int cb_close(void *stream) {
121 istream *in = (istream *)stream;
122 nassertr(in != nullptr, EOF);
123
125 vfs->close_read_file(in);
126 return 0;
127}
128
129static const OpusFileCallbacks callbacks = {cb_read, cb_seek, cb_tell, cb_close};
130
131TypeHandle OpusAudioCursor::_type_handle;
132
133/**
134 * Reads the .wav header from the indicated stream. This leaves the read
135 * pointer positioned at the start of the data.
136 */
137OpusAudioCursor::
138OpusAudioCursor(OpusAudio *src, istream *stream) :
139 MovieAudioCursor(src),
140 _is_valid(false),
141 _link(0)
142{
143 nassertv(stream != nullptr);
144 nassertv(stream->good());
145
146 int error = 0;
147 _op = op_open_callbacks((void *)stream, &callbacks, nullptr, 0, &error);
148 if (_op == nullptr) {
149 movies_cat.error()
150 << "Failed to read Opus file (error code " << error << ").\n";
151 return;
152 }
153
154 ogg_int64_t samples = op_pcm_total(_op, -1);
155 if (samples != OP_EINVAL) {
156 // Opus timestamps are fixed at 48 kHz.
157 _length = (double)samples / 48000.0;
158 }
159
160 _audio_channels = op_channel_count(_op, -1);
161 _audio_rate = 48000;
162
163 _can_seek = opus_enable_seek && op_seekable(_op);
164 _can_seek_fast = _can_seek;
165
166 _is_valid = true;
167}
168
169/**
170 * xxx
171 */
172OpusAudioCursor::
173~OpusAudioCursor() {
174 if (_op != nullptr) {
175 op_free(_op);
176 _op = nullptr;
177 }
178}
179
180/**
181 * Seeks to a target location. Afterward, the packet_time is guaranteed to be
182 * less than or equal to the specified time.
183 */
184void OpusAudioCursor::
185seek(double t) {
186 if (!opus_enable_seek) {
187 return;
188 }
189
190 t = std::max(t, 0.0);
191
192 // Use op_time_seek_lap if cross-lapping is enabled.
193 int error = op_pcm_seek(_op, (ogg_int64_t)(t * 48000.0));
194 if (error != 0) {
195 movies_cat.error()
196 << "Seek failed (error " << error << "). Opus stream may not be seekable.\n";
197 return;
198 }
199
200 _last_seek = op_pcm_tell(_op) / 48000.0;
201 _samples_read = 0;
202}
203
204/**
205 * Read audio samples from the stream. N is the number of samples you wish to
206 * read. Your buffer must be equal in size to N * channels. Multiple-channel
207 * audio will be interleaved.
208 */
209void OpusAudioCursor::
210read_samples(int n, int16_t *data) {
211 int16_t *end = data + (n * _audio_channels);
212
213 while (data < end) {
214 // op_read gives it to us in the exact format we need. Nifty!
215 int link;
216 int read_samples = op_read(_op, data, end - data, &link);
217 if (read_samples > 0) {
218 data += read_samples * _audio_channels;
219 _samples_read += read_samples;
220 } else {
221 break;
222 }
223
224 if (_link != link) {
225 // It is technically possible for it to change parameters from one link
226 // to the next. However, we don't offer this flexibility.
227 int channels = op_channel_count(_op, link);
228 if (channels != _audio_channels) {
229 movies_cat.error()
230 << "Opus file has inconsistent channel count!\n";
231
232 // We'll change it anyway. Not sure what happens next.
233 _audio_channels = channels;
234 }
235
236 _link = link;
237 }
238 }
239
240 // Fill the rest of the buffer with silence.
241 if (data < end) {
242 memset(data, 0, (unsigned char *)end - (unsigned char *)data);
243 }
244}
245
246#endif // HAVE_OPUS
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.