Panda3D
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 
25 using std::istream;
26 
27 /**
28  * Callbacks passed to libopusfile to implement file I/O via the
29  * VirtualFileSystem.
30  */
31 int 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 
45 int 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 
113 opus_int64 cb_tell(void *stream) {
114  istream *in = (istream *)stream;
115  nassertr(in != nullptr, -1);
116 
117  return in->tellg();
118 }
119 
120 int 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 
129 static const OpusFileCallbacks callbacks = {cb_read, cb_seek, cb_tell, cb_close};
130 
131 TypeHandle 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  */
137 OpusAudioCursor::
138 OpusAudioCursor(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  */
172 OpusAudioCursor::
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  */
184 void OpusAudioCursor::
185 seek(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  */
209 void OpusAudioCursor::
210 read_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.