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
opusAudio.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
opusAudioCursor.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