Browse Source

Support loading Opus audio files via libopusfile.

rdb 8 years ago
parent
commit
828f1c10ca

+ 6 - 0
dtool/src/parser-inc/opus/opus.h

@@ -0,0 +1,6 @@
+#ifndef OPUS_H
+#define OPUS_H
+
+#include "opus_types.h"
+
+#endif

+ 19 - 0
dtool/src/parser-inc/opus/opus_types.h

@@ -0,0 +1,19 @@
+#ifndef OPUS_TYPES_H
+#define OPUS_TYPES_H
+
+#include <stdint.h>
+
+typedef int16_t opus_int16;
+typedef uint16_t opus_uint16;
+typedef int32_t opus_int32;
+typedef uint32_t opus_uint32;
+
+#define opus_int int
+#define opus_int64 long long
+#define opus_int8 signed char
+
+#define opus_uint unsigned int
+#define opus_uint64 unsigned long long
+#define opus_uint8 unsigned char
+
+#endif

+ 8 - 0
dtool/src/parser-inc/opus/opusfile.h

@@ -0,0 +1,8 @@
+#include "opus.h"
+
+typedef struct OpusHead          OpusHead;
+typedef struct OpusTags          OpusTags;
+typedef struct OpusPictureTag    OpusPictureTag;
+typedef struct OpusServerInfo    OpusServerInfo;
+typedef struct OpusFileCallbacks OpusFileCallbacks;
+typedef struct OggOpusFile       OggOpusFile;

+ 16 - 6
makepanda/makepanda.py

@@ -77,7 +77,7 @@ PkgListSet(["PYTHON", "DIRECT",                        # Python support
   "EGL",                                               # OpenGL (ES) integration
   "EIGEN",                                             # Linear algebra acceleration
   "OPENAL", "FMODEX",                                  # Audio playback
-  "VORBIS", "FFMPEG", "SWSCALE", "SWRESAMPLE",         # Audio decoding
+  "VORBIS", "OPUS", "FFMPEG", "SWSCALE", "SWRESAMPLE", # Audio decoding
   "ODE", "PHYSX", "BULLET", "PANDAPHYSICS",            # Physics
   "SPEEDTREE",                                         # SpeedTree
   "ZLIB", "PNG", "JPEG", "TIFF", "OPENEXR", "SQUISH",  # 2D Formats support
@@ -700,6 +700,10 @@ if (COMPILER == "MSVC"):
         LibName("VORBIS",   GetThirdpartyDir() + "vorbis/lib/libogg_static.lib")
         LibName("VORBIS",   GetThirdpartyDir() + "vorbis/lib/libvorbis_static.lib")
         LibName("VORBIS",   GetThirdpartyDir() + "vorbis/lib/libvorbisfile_static.lib")
+    if (PkgSkip("OPUS")==0):
+        LibName("OPUS", GetThirdpartyDir() + "opus/lib/libogg_static.lib")
+        LibName("OPUS", GetThirdpartyDir() + "opus/lib/libopus_static.lib")
+        LibName("OPUS", GetThirdpartyDir() + "opus/lib/libopusfile_static.lib")
     for pkg in MAYAVERSIONS:
         if (PkgSkip(pkg)==0):
             LibName(pkg, '"' + SDK[pkg] + '/lib/Foundation.lib"')
@@ -819,6 +823,7 @@ if (COMPILER=="GCC"):
         SmartPkgEnable("VRPN",      "",          ("vrpn", "quat"), ("vrpn", "quat.h", "vrpn/vrpn_Types.h"))
         SmartPkgEnable("BULLET", "bullet", ("BulletSoftBody", "BulletDynamics", "BulletCollision", "LinearMath"), ("bullet", "bullet/btBulletDynamicsCommon.h"))
         SmartPkgEnable("VORBIS",    "vorbisfile",("vorbisfile", "vorbis", "ogg"), ("ogg/ogg.h", "vorbis/vorbisfile.h"))
+        SmartPkgEnable("OPUS",      "opusfile",  ("opusfile", "opus", "ogg"), ("ogg/ogg.h", "opus/opusfile.h"))
         SmartPkgEnable("JPEG",      "",          ("jpeg"), "jpeglib.h")
         SmartPkgEnable("PNG",       "libpng",    ("png"), "png.h", tool = "libpng-config")
 
@@ -2236,6 +2241,7 @@ DTOOL_CONFIG=[
     ("HAVE_PNM",                       '1',                      '1'),
     ("HAVE_STB_IMAGE",                 '1',                      '1'),
     ("HAVE_VORBIS",                    'UNDEF',                  'UNDEF'),
+    ("HAVE_OPUS",                      'UNDEF',                  'UNDEF'),
     ("HAVE_FREETYPE",                  'UNDEF',                  'UNDEF'),
     ("HAVE_FFTW",                      'UNDEF',                  'UNDEF'),
     ("HAVE_OPENSSL",                   'UNDEF',                  'UNDEF'),
@@ -3880,10 +3886,10 @@ if (not RUNTIME):
 #
 
 if (not RUNTIME):
-  OPTS=['DIR:panda/src/movies', 'BUILDING:PANDA', 'VORBIS']
+  OPTS=['DIR:panda/src/movies', 'BUILDING:PANDA', 'VORBIS', 'OPUS']
   TargetAdd('p3movies_composite1.obj', opts=OPTS, input='p3movies_composite1.cxx')
 
-  OPTS=['DIR:panda/src/movies', 'VORBIS', 'PYTHON']
+  OPTS=['DIR:panda/src/movies', 'VORBIS', 'OPUS', 'PYTHON']
   IGATEFILES=GetDirectoryContents('panda/src/movies', ["*.h", "*_composite*.cxx"])
   TargetAdd('libp3movies.in', opts=OPTS, input=IGATEFILES)
   TargetAdd('libp3movies.in', opts=['IMOD:panda3d.core', 'ILIB:libp3movies', 'SRCDIR:panda/src/movies'])
@@ -4017,7 +4023,7 @@ if (not RUNTIME):
 if (not RUNTIME):
   OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', 'HARFBUZZ',
       'TIFF', 'OPENEXR', 'ZLIB', 'OPENSSL', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2',
-      'SQUISH', 'NVIDIACG', 'VORBIS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI']
+      'SQUISH', 'NVIDIACG', 'VORBIS', 'OPUS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI']
 
   TargetAdd('panda_panda.obj', opts=OPTS, input='panda.cxx')
 
@@ -7226,10 +7232,14 @@ def MakeInstallerOSX():
 
     if not PkgSkip("FFMPEG"):
         dist.write('    <choice id="ffmpeg" title="FFMpeg Plug-In" tooltip="FFMpeg video and audio decoding plug-in" description="This package contains the FFMpeg plug-in, which is used for decoding video and audio files with OpenAL.')
-        if PkgSkip("VORBIS"):
+        if PkgSkip("VORBIS") and PkgSkip("OPUS"):
             dist.write('  It is not required for loading .wav files, which Panda3D can read out of the box.">\n')
-        else:
+        elif PkgSkip("VORBIS"):
+            dist.write('  It is not required for loading .wav or .opus files, which Panda3D can read out of the box.">\n')
+        elif PkgSkip("OPUS"):
             dist.write('  It is not required for loading .wav or .ogg files, which Panda3D can read out of the box.">\n')
+        else:
+            dist.write('  It is not required for loading .wav, .ogg or .opus files, which Panda3D can read out of the box.">\n')
         dist.write('        <pkg-ref id="org.panda3d.panda3d.ffmpeg.pkg"/>\n')
         dist.write('    </choice>\n')
 

+ 16 - 0
panda/src/movies/config_movies.cxx

@@ -23,6 +23,8 @@
 #include "movieTypeRegistry.h"
 #include "movieVideo.h"
 #include "movieVideoCursor.h"
+#include "opusAudio.h"
+#include "opusAudioCursor.h"
 #include "userDataAudio.h"
 #include "userDataAudioCursor.h"
 #include "vorbisAudio.h"
@@ -51,6 +53,11 @@ ConfigVariableList load_video_type
           "either the name of a module, or a space-separate list of filename "
           "extensions, followed by the name of the module."));
 
+ConfigVariableBool opus_enable_seek
+("opus-enable-seek", true,
+ PRC_DESC("Set this to false if you're having trouble with seeking while "
+          "using the Opus decoder."));
+
 ConfigVariableBool vorbis_enable_seek
 ("vorbis-enable-seek", true,
  PRC_DESC("Set this to false if you're having trouble with seeking while "
@@ -91,6 +98,11 @@ init_libmovies() {
   WavAudio::init_type();
   WavAudioCursor::init_type();
 
+#ifdef HAVE_OPUS
+  OpusAudio::init_type();
+  OpusAudioCursor::init_type();
+#endif
+
 #ifdef HAVE_VORBIS
   VorbisAudio::init_type();
   VorbisAudioCursor::init_type();
@@ -100,6 +112,10 @@ init_libmovies() {
   reg->register_audio_type(&FlacAudio::make, "flac");
   reg->register_audio_type(&WavAudio::make, "wav wave");
 
+#ifdef HAVE_OPUS
+  reg->register_audio_type(&OpusAudio::make, "opus");
+#endif
+
 #ifdef HAVE_VORBIS
   reg->register_audio_type(&VorbisAudio::make, "ogg oga");
 #endif

+ 2 - 0
panda/src/movies/config_movies.h

@@ -27,6 +27,8 @@ NotifyCategoryDecl(movies, EXPCL_PANDA_MOVIES, EXPTP_PANDA_MOVIES);
 extern ConfigVariableList load_audio_type;
 extern ConfigVariableList load_video_type;
 
+extern ConfigVariableBool opus_enable_seek;
+
 extern ConfigVariableBool vorbis_enable_seek;
 extern ConfigVariableBool vorbis_seek_lap;
 

+ 12 - 0
panda/src/movies/opusAudio.I

@@ -0,0 +1,12 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file opusAudio.I
+ * @author rdb
+ * @date 2017-05-24
+ */

+ 68 - 0
panda/src/movies/opusAudio.cxx

@@ -0,0 +1,68 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file opusAudio.cxx
+ * @author rdb
+ * @date 2017-05-24
+ */
+
+#include "opusAudio.h"
+#include "opusAudioCursor.h"
+#include "virtualFileSystem.h"
+#include "dcast.h"
+
+#ifdef HAVE_OPUS
+
+TypeHandle OpusAudio::_type_handle;
+
+/**
+ * xxx
+ */
+OpusAudio::
+OpusAudio(const Filename &name) :
+  MovieAudio(name)
+{
+  _filename = name;
+}
+
+/**
+ * xxx
+ */
+OpusAudio::
+~OpusAudio() {
+}
+
+/**
+ * Open this audio, returning a MovieAudioCursor
+ */
+PT(MovieAudioCursor) OpusAudio::
+open() {
+  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+  istream *stream = vfs->open_read_file(_filename, true);
+
+  if (stream == nullptr) {
+    return nullptr;
+  } else {
+    PT(OpusAudioCursor) cursor = new OpusAudioCursor(this, stream);
+    if (cursor == nullptr || !cursor->_is_valid) {
+      return nullptr;
+    } else {
+      return DCAST(MovieAudioCursor, cursor);
+    }
+  }
+}
+
+/**
+ * Obtains a MovieAudio that references a file.
+ */
+PT(MovieAudio) OpusAudio::
+make(const Filename &name) {
+  return DCAST(MovieAudio, new OpusAudio(name));
+}
+
+#endif // HAVE_OPUS

+ 61 - 0
panda/src/movies/opusAudio.h

@@ -0,0 +1,61 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file opusAudio.h
+ * @author rdb
+ * @date 2017-05-24
+ */
+
+#ifndef OPUSAUDIO_H
+#define OPUSAUDIO_H
+
+#include "pandabase.h"
+#include "movieAudio.h"
+
+#ifdef HAVE_OPUS
+
+class OpusAudioCursor;
+
+/**
+ * Interfaces with the libopusfile library to implement decoding of Opus
+ * audio files.
+ */
+class EXPCL_PANDA_MOVIES OpusAudio : public MovieAudio {
+PUBLISHED:
+  OpusAudio(const Filename &name);
+  virtual ~OpusAudio();
+  virtual PT(MovieAudioCursor) open();
+
+  static PT(MovieAudio) make(const Filename &name);
+
+private:
+  friend class OpusAudioCursor;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedWritableReferenceCount::init_type();
+    register_type(_type_handle, "OpusAudio",
+                  MovieAudio::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "opusAudio.I"
+
+#endif // HAVE_OPUS
+
+#endif // OPUSAUDIO_H

+ 12 - 0
panda/src/movies/opusAudioCursor.I

@@ -0,0 +1,12 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file opusAudioCursor.I
+ * @author rdb
+ * @date 2017-05-24
+ */

+ 224 - 0
panda/src/movies/opusAudioCursor.cxx

@@ -0,0 +1,224 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file opusAudioCursor.cxx
+ * @author rdb
+ * @date 2017-05-24
+ */
+
+#include "opusAudioCursor.h"
+#include "virtualFileSystem.h"
+
+#ifdef HAVE_OPUS
+
+#include <opus/opusfile.h>
+
+/**
+ * Callbacks passed to libopusfile to implement file I/O via the
+ * VirtualFileSystem.
+ */
+int cb_read(void *stream, unsigned char *ptr, int nbytes) {
+  istream *in = (istream *)stream;
+  nassertr(in != nullptr, -1);
+
+  in->read((char *)ptr, nbytes);
+
+  if (in->eof()) {
+    // Gracefully handle EOF.
+    in->clear();
+  }
+
+  return in->gcount();
+}
+
+int cb_seek(void *stream, opus_int64 offset, int whence) {
+  if (!opus_enable_seek) {
+    return -1;
+  }
+
+  istream *in = (istream *)stream;
+  nassertr(in != nullptr, -1);
+
+  switch (whence) {
+  case SEEK_SET:
+    in->seekg(offset, ios::beg);
+    break;
+
+  case SEEK_CUR:
+    in->seekg(offset, ios::cur);
+    break;
+
+  case SEEK_END:
+    in->seekg(offset, ios::end);
+    break;
+
+  default:
+    movies_cat.error()
+      << "Illegal parameter to seek in cb_seek\n";
+    return -1;
+  }
+
+  if (in->fail()) {
+    movies_cat.error()
+      << "Failure to seek to byte " << offset;
+
+    switch (whence) {
+    case SEEK_CUR:
+      movies_cat.error(false)
+        << " from current location!\n";
+      break;
+
+    case SEEK_END:
+      movies_cat.error(false)
+        << " from end of file!\n";
+      break;
+
+    default:
+      movies_cat.error(false) << "!\n";
+    }
+
+    return -1;
+  }
+
+  return 0;
+}
+
+opus_int64 cb_tell(void *stream) {
+  istream *in = (istream *)stream;
+  nassertr(in != nullptr, -1);
+
+  return in->tellg();
+}
+
+int cb_close(void *stream) {
+  istream *in = (istream *)stream;
+  nassertr(in != nullptr, EOF);
+
+  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+  vfs->close_read_file(in);
+  return 0;
+}
+
+static const OpusFileCallbacks callbacks = {cb_read, cb_seek, cb_tell, cb_close};
+
+TypeHandle OpusAudioCursor::_type_handle;
+
+/**
+ * Reads the .wav header from the indicated stream.  This leaves the read
+ * pointer positioned at the start of the data.
+ */
+OpusAudioCursor::
+OpusAudioCursor(OpusAudio *src, istream *stream) :
+  MovieAudioCursor(src),
+  _is_valid(false),
+  _link(0)
+{
+  nassertv(stream != nullptr);
+  nassertv(stream->good());
+
+  int error = 0;
+  _op = op_open_callbacks((void *)stream, &callbacks, nullptr, 0, &error);
+  if (_op == nullptr) {
+    movies_cat.error()
+      << "Failed to read Opus file (error code " << error << ").\n";
+    return;
+  }
+
+  ogg_int64_t samples = op_pcm_total(_op, -1);
+  if (samples != OP_EINVAL) {
+    // Opus timestamps are fixed at 48 kHz.
+    _length = (double)samples / 48000.0;
+  }
+
+  _audio_channels = op_channel_count(_op, -1);
+  _audio_rate = 48000;
+
+  _can_seek = opus_enable_seek && op_seekable(_op);
+  _can_seek_fast = _can_seek;
+
+  _is_valid = true;
+}
+
+/**
+ * xxx
+ */
+OpusAudioCursor::
+~OpusAudioCursor() {
+  if (_op != nullptr) {
+    op_free(_op);
+    _op = nullptr;
+  }
+}
+
+/**
+ * Seeks to a target location.  Afterward, the packet_time is guaranteed to be
+ * less than or equal to the specified time.
+ */
+void OpusAudioCursor::
+seek(double t) {
+  if (!opus_enable_seek) {
+    return;
+  }
+
+  t = max(t, 0.0);
+
+  // Use op_time_seek_lap if cross-lapping is enabled.
+  int error = op_pcm_seek(_op, (ogg_int64_t)(t * 48000.0));
+  if (error != 0) {
+    movies_cat.error()
+      << "Seek failed (error " << error << ").  Opus stream may not be seekable.\n";
+    return;
+  }
+
+  _last_seek = op_pcm_tell(_op) / 48000.0;
+  _samples_read = 0;
+}
+
+/**
+ * Read audio samples from the stream.  N is the number of samples you wish to
+ * read.  Your buffer must be equal in size to N * channels.  Multiple-channel
+ * audio will be interleaved.
+ */
+void OpusAudioCursor::
+read_samples(int n, int16_t *data) {
+  int16_t *end = data + (n * _audio_channels);
+
+  while (data < end) {
+    // op_read gives it to us in the exact format we need.  Nifty!
+    int link;
+    int read_samples = op_read(_op, data, end - data, &link);
+    if (read_samples > 0) {
+      data += read_samples * _audio_channels;
+      _samples_read += read_samples;
+    } else {
+      break;
+    }
+
+    if (_link != link) {
+      // It is technically possible for it to change parameters from one link
+      // to the next.  However, we don't offer this flexibility.
+      int channels = op_channel_count(_op, link);
+      if (channels != _audio_channels) {
+        movies_cat.error()
+          << "Opus file has inconsistent channel count!\n";
+
+        // We'll change it anyway.  Not sure what happens next.
+        _audio_channels = channels;
+      }
+
+      _link = link;
+    }
+  }
+
+  // Fill the rest of the buffer with silence.
+  if (data < end) {
+    memset(data, 0, (unsigned char *)end - (unsigned char *)data);
+  }
+}
+
+#endif // HAVE_OPUS

+ 78 - 0
panda/src/movies/opusAudioCursor.h

@@ -0,0 +1,78 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file opusAudioCursor.h
+ * @author rdb
+ * @date 2017-05-24
+ */
+
+#ifndef OPUSAUDIOCURSOR_H
+#define OPUSAUDIOCURSOR_H
+
+#include "pandabase.h"
+#include "movieAudioCursor.h"
+
+#ifdef HAVE_OPUS
+
+#include <ogg/ogg.h>
+
+typedef struct OggOpusFile OggOpusFile;
+
+class OpusAudio;
+
+/**
+ * Interfaces with the libopusfile library to implement decoding of Opus
+ * audio files.
+ */
+class EXPCL_PANDA_MOVIES OpusAudioCursor : public MovieAudioCursor {
+PUBLISHED:
+  OpusAudioCursor(OpusAudio *src, istream *stream);
+  virtual ~OpusAudioCursor();
+  virtual void seek(double offset);
+
+public:
+  virtual void read_samples(int n, int16_t *data);
+
+  bool _is_valid;
+
+protected:
+  OggOpusFile *_op;
+
+  int _link;
+  double _byte_rate;
+  int _block_align;
+  int _bytes_per_sample;
+  bool _is_float;
+
+  streampos _data_start;
+  streampos _data_pos;
+  size_t _data_size;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    MovieAudioCursor::init_type();
+    register_type(_type_handle, "OpusAudioCursor",
+                  MovieAudioCursor::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "opusAudioCursor.I"
+
+#endif // HAVE_OPUS
+
+#endif // OPUSAUDIOCURSOR_H

+ 2 - 0
panda/src/movies/p3movies_composite1.cxx

@@ -10,6 +10,8 @@
 #include "movieTypeRegistry.cxx"
 #include "movieVideo.cxx"
 #include "movieVideoCursor.cxx"
+#include "opusAudio.cxx"
+#include "opusAudioCursor.cxx"
 #include "userDataAudio.cxx"
 #include "userDataAudioCursor.cxx"
 #include "vorbisAudio.cxx"