Support loading Opus audio files via libopusfile.

This commit is contained in:
rdb 2017-05-24 22:19:56 +02:00
parent 257311cc0c
commit 828f1c10ca
13 changed files with 524 additions and 6 deletions

View File

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

View File

@ -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

View File

@ -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;

View File

@ -77,7 +77,7 @@ PkgListSet(["PYTHON", "DIRECT", # Python support
"EGL", # OpenGL (ES) integration "EGL", # OpenGL (ES) integration
"EIGEN", # Linear algebra acceleration "EIGEN", # Linear algebra acceleration
"OPENAL", "FMODEX", # Audio playback "OPENAL", "FMODEX", # Audio playback
"VORBIS", "FFMPEG", "SWSCALE", "SWRESAMPLE", # Audio decoding "VORBIS", "OPUS", "FFMPEG", "SWSCALE", "SWRESAMPLE", # Audio decoding
"ODE", "PHYSX", "BULLET", "PANDAPHYSICS", # Physics "ODE", "PHYSX", "BULLET", "PANDAPHYSICS", # Physics
"SPEEDTREE", # SpeedTree "SPEEDTREE", # SpeedTree
"ZLIB", "PNG", "JPEG", "TIFF", "OPENEXR", "SQUISH", # 2D Formats support "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/libogg_static.lib")
LibName("VORBIS", GetThirdpartyDir() + "vorbis/lib/libvorbis_static.lib") LibName("VORBIS", GetThirdpartyDir() + "vorbis/lib/libvorbis_static.lib")
LibName("VORBIS", GetThirdpartyDir() + "vorbis/lib/libvorbisfile_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: for pkg in MAYAVERSIONS:
if (PkgSkip(pkg)==0): if (PkgSkip(pkg)==0):
LibName(pkg, '"' + SDK[pkg] + '/lib/Foundation.lib"') 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("VRPN", "", ("vrpn", "quat"), ("vrpn", "quat.h", "vrpn/vrpn_Types.h"))
SmartPkgEnable("BULLET", "bullet", ("BulletSoftBody", "BulletDynamics", "BulletCollision", "LinearMath"), ("bullet", "bullet/btBulletDynamicsCommon.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("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("JPEG", "", ("jpeg"), "jpeglib.h")
SmartPkgEnable("PNG", "libpng", ("png"), "png.h", tool = "libpng-config") SmartPkgEnable("PNG", "libpng", ("png"), "png.h", tool = "libpng-config")
@ -2236,6 +2241,7 @@ DTOOL_CONFIG=[
("HAVE_PNM", '1', '1'), ("HAVE_PNM", '1', '1'),
("HAVE_STB_IMAGE", '1', '1'), ("HAVE_STB_IMAGE", '1', '1'),
("HAVE_VORBIS", 'UNDEF', 'UNDEF'), ("HAVE_VORBIS", 'UNDEF', 'UNDEF'),
("HAVE_OPUS", 'UNDEF', 'UNDEF'),
("HAVE_FREETYPE", 'UNDEF', 'UNDEF'), ("HAVE_FREETYPE", 'UNDEF', 'UNDEF'),
("HAVE_FFTW", 'UNDEF', 'UNDEF'), ("HAVE_FFTW", 'UNDEF', 'UNDEF'),
("HAVE_OPENSSL", 'UNDEF', 'UNDEF'), ("HAVE_OPENSSL", 'UNDEF', 'UNDEF'),
@ -3880,10 +3886,10 @@ if (not RUNTIME):
# #
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') 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"]) IGATEFILES=GetDirectoryContents('panda/src/movies', ["*.h", "*_composite*.cxx"])
TargetAdd('libp3movies.in', opts=OPTS, input=IGATEFILES) TargetAdd('libp3movies.in', opts=OPTS, input=IGATEFILES)
TargetAdd('libp3movies.in', opts=['IMOD:panda3d.core', 'ILIB:libp3movies', 'SRCDIR:panda/src/movies']) TargetAdd('libp3movies.in', opts=['IMOD:panda3d.core', 'ILIB:libp3movies', 'SRCDIR:panda/src/movies'])
@ -4017,7 +4023,7 @@ if (not RUNTIME):
if (not RUNTIME): if (not RUNTIME):
OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', 'HARFBUZZ', OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', 'HARFBUZZ',
'TIFF', 'OPENEXR', 'ZLIB', 'OPENSSL', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2', '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') TargetAdd('panda_panda.obj', opts=OPTS, input='panda.cxx')
@ -7226,10 +7232,14 @@ def MakeInstallerOSX():
if not PkgSkip("FFMPEG"): 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.') 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') 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') 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(' <pkg-ref id="org.panda3d.panda3d.ffmpeg.pkg"/>\n')
dist.write(' </choice>\n') dist.write(' </choice>\n')

View File

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

View File

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

View File

@ -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
*/

View File

@ -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

View File

@ -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

View File

@ -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
*/

View File

@ -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

View File

@ -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

View File

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