MP4 support v2 (undebugged). This replaces callouts to faad with use of libfaad (aka neaacdec) and loads all components dynamically, much like existing MP1/2/3 support.

This commit is contained in:
Chris Smowton 2014-12-21 20:13:58 +00:00
parent aae6763f4f
commit f63b14cca9
8 changed files with 388 additions and 104 deletions

View File

@ -216,18 +216,17 @@ fi
if test -z $MP4V2_DISABLED ; then
AC_CHECK_HEADER(mp4v2/mp4v2.h,[MP4V2_HEADER_FOUND=yes],[])
if test $MP4V2_HEADER_FOUND ; then
AC_CHECK_LIB(mp4v2,MP4Read,[MP4V2_FOUND=yes],[])
fi
if test $MP4V2_FOUND ; then
MP4V2_LIBS="-lmp4v2"
AC_DEFINE(HAVE_MP4V2)
fi
AC_CHECK_HEADER(neaacdec.h,[MP4V2_FOUND=yes],[])
if test $MP4V2_FOUND ; then
AC_DEFINE(HAVE_MP4_LIBS)
fi
fi
fi
#
# Set Hard Library Dependencies
#
AC_SUBST(LIB_RDLIBS,"-lm -lpthread -lqui -lrd -lcurl -lid3 $FLAC_LIBS $MP4V2_LIBS -lsndfile -lsamplerate -lcdda_interface -lcdda_paranoia -lcrypt -ldl -lpam -lSoundTouch")
AC_SUBST(LIB_RDLIBS,"-lm -lpthread -lqui -lrd -lcurl -lid3 $FLAC_LIBS -lsndfile -lsamplerate -lcdda_interface -lcdda_paranoia -lcrypt -ldl -lpam -lSoundTouch")
#
# Setup MPEG Dependencies

View File

@ -176,6 +176,7 @@ dist_librd_la_SOURCES = dbversion.h\
rdmeteraverage.cpp rdmeteraverage.h\
rdmixer.cpp rdmixer.h\
rdmonitor_config.cpp rdmonitor_config.h\
rdmp4.cpp rdmp4.h\
rdnownext.cpp rdnownext.h\
rdoneshot.cpp rdoneshot.h\
rdpam.cpp rdpam.h\

View File

@ -42,6 +42,10 @@
#include <FLAC++/encoder.h>
#include <rdflacdecode.h>
#endif // HAVE_FLAC
#ifdef HAVE_MP4_LIBS
#include <mp4v2/mp4v2.h>
#include <neaacdec.h>
#endif // HAVE_MP4_LIBS
#include <id3/tag.h>
#include <qfile.h>
@ -680,72 +684,160 @@ RDAudioConvert::ErrorCode RDAudioConvert::Stage1Mpeg(const QString &dstfile,
#endif // HAVE_MAD
}
// Based on libfaad's frontend/main.c, but using libmp4v2 for MP4 access.
RDAudioConvert::ErrorCode RDAudioConvert::Stage1M4A(const QString &dstfile,
RDWaveFile *wave) {
const char* args[7];
int childstatus = 0;
pid_t child = fork();
QString tmpname = dstfile + ".m4a_temp.wav";
if(child == 0) {
freopen("/dev/null", "w", stdout);
freopen("/dev/null", "w", stderr);
args[0] = "faad";
args[1] = wave->getName();
args[2] = "-o";
args[3] = tmpname;
args[4] = "-b";
args[5] = "4"; // Emit a Float32 format wave file, like the other stage 1s.
args[6] = 0;
execvp("faad", (char* const*)args);
exit(109);
RDWaveFile *wave)
{
#ifdef HAVE_MP4_LIBS
SNDFILE *sf_dst=NULL;
SF_INFO sf_dst_info;
int ret = RDAudioConvert::ErrorOk;
if(!LoadMP4Libs()) {
return RDAudioConvert::ErrorFormatNotSupported;
}
else {
waitpid(child, &childstatus, 0);
// Killed by a signal (e.g. OOM?)
if(!WIFEXITED(childstatus)) {
unlink(tmpname);
return RDAudioConvert::ErrorInternal;
}
// Returned 109, probably because we could't find faad?
if(WEXITSTATUS(childstatus) == 109)
return RDAudioConvert::ErrorFormatNotSupported;
// Two elements are missing:
// 1. need to measure the peak amplitude;
// 2. need to trim if a subrange was requested.
// For now just use the sndfile import path to accomplish both tasks.
{
SF_INFO sf_src_info;
SNDFILE* sf_src;
memset(&sf_src_info, 0, sizeof(sf_src_info));
// If this fails it is likely because faad could not decode.
if((sf_src = sf_open(tmpname, SFM_READ, &sf_src_info)) == NULL)
return RDAudioConvert::ErrorFormatError;
RDAudioConvert::ErrorCode err = Stage1SndFile(dstfile, sf_src, &sf_src_info);
sf_close(sf_src);
unlink(tmpname);
return err;
//
// Open source
//
MP4FileHandle f = dlmp4.MP4Read(getName());
if(f == MP4_INVALID_FILE_HANDLE)
return RDAudioConvert::ErrorNoSource;
MP4TrackId audioTrack = dlmp4.getMP4AACTrack(f);
MP4SampleId firstSample = 0;
MP4SampleId lastSample = dlmp4.MP4GetTrackNumberOfSamples(f, audioTrack) - 1;
if(conv_start_point > 0) {
double startsecs = ((double)conv_start_point) / 1000;
MP4Timestamp startts = (MP4Timestamp)(startsecs * wave->getSamplesPerSec());
firstSample = dlmp4.MP4GetSampleIdFromTime(f, audioTrack, startts, /*need_sync=*/false);
if(firstSample == MP4_INVALID_SAMPLE_ID) {
ret = RDAudioConvert::ErrorInvalidSource;
goto out_mp4;
}
}
if(conv_end_point > 0) {
double stopsecs = ((double)conv_end_point) / 1000;
MP4Timestamp stopts = (MP4Timestamp)(stopsecs * wave->getSamplesPerSec());
lastSample = dlmp4.MP4GetSampleIdFromTime(f, audioTrack, stopts, /*need_sync=*/false);
if(lastSample == MP4_INVALID_SAMPLE_ID) {
ret = RDAudioConvert::ErrorInvalidSource;
goto out_mp4;
}
}
uint32_t aacBufSize = dlmp4.MP4GetTrackMaxSampleSize(f, audioTrack);
uint8_t* aacBuf = malloc(aacBufSize);
if(!aacBufSize || !aacBuf) {
// Probably the source's fault for specifying a massive buffer.
ret = RDAudioConvert::ErrorInvalidSource;
goto out_mp4;
}
uint8_t* aacConfigBuffer;
uint32_t* aacConfigSize;
dlmp4.MP4GetTrackESConfiguration(f, audioTrack, &aacConfigBuffer, &aacConfigSize);
if(!aacConfigBuffer) {
ret = RDAudioConvert::ErrorInvalidSource;
goto out_mp4_buf;
}
//
// Open Destination
//
memset(&sf_dst_info,0,sizeof(sf_dst_info));
sf_dst_info.format=SF_FORMAT_WAV|SF_FORMAT_FLOAT;
sf_dst_info.channels=wave->getChannels();
sf_dst_info.samplerate=wave->getSamplesPerSec();
if((sf_dst=sf_open(dstfile,SFM_WRITE,&sf_dst_info))==NULL) {
ret = RDAudioConvert::ErrorNoDestination;
goto out_mp4_configbuf;
}
sf_command(sf_dst,SFC_SET_NORM_DOUBLE,NULL,SF_FALSE);
//
// Initialize Decoder
//
NeAACDecHandle hDecoder = dlmp4.NeAACDecOpen();
NeAACDecConfigurationPtr config = dlmp4.NeAACDecGetCurrentConfiguration(hDecoder);
config->outputFormat = FAAD_FMT_FLOAT;
config->downMatrix = 1; // Downmix >2 channels to stereo.
if(!dlmp4.NeAACDecSetConfiguration(hDecoder, config)) {
ret = RDAudioConvert::ErrorInvalidSource;
goto out_decoder;
}
unsigned long foundSampleRate;
unsigned char foundChannels;
if(dlmp4.NeAACDecInit2(hDecoder, aacConfigBuffer, aacConfigSize, &foundSampleRate, &foundChannels) < 0) {
ret = RDAudioConvert::ErrorInvalidSource;
goto out_decoder;
}
if(foundSampleRate != wave->getSamplesPerSec() || foundChannels != wave->getChannels()) {
fprintf(stderr, "M4A header information inconsistent with actual file? Header: %lu/%u; file: %lu/%u\n",
wave->getSamplesPerSec(), (unsigned)wave->getChannels(), foundSampleRate, (unsigned)foundChannels);
ret = RDAudioConvert::ErrorInvalidSource;
goto out_decoder;
}
//
// Decode
//
for(MP4SampleId i = firstSample; i <= lastSample; ++i) {
uint32_t aacBytes = aacBufSize;
if(!dlmp4.MP4ReadSample(f, audioTrack, i, &aacBuf, &aacBytes, 0, 0, 0, 0)) {
ret = RDAudioConvert::ErrorInvalidSource;
break;
}
NeAACDecFrameInfo frameInfo;
// The library docs are not clear about the lifetime or cleanup of sample_buffer.
// I hope it lives until the next NeAACDecDecode call, and is cleaned up by NeAACDecClose
void* sample_buffer = dlmp4.NeAACDecDecode(hDecoder, &frameInfo, aacBuf, aacBytes);
if(!sample_buffer) {
ret = RDAudioConvert::ErrorInvalidSource;
break;
}
UpdatePeak(sample_buffer, frameInfo.samples);
if(sf_write_float(dst_sf, sample_buffer, frameInfo.samples) != frameInfo.samples) {
ret = RDAudioConvert::ErrorInternal;
break;
}
}
//
// Cleanup
//
out_decoder:
dlmp4.NeAACDecClose(hDecoder);
out_sf:
sf_close(sf_dst);
out_mp4_configbuf:
free(aacConfigBuffer);
out_mp4_buf:
free(aacBuf);
out_mp4:
dlmp4.MP4Close(f, 0);
return ret;
#else
return RDAudioConvert::ErrorFormatNotSupported;
#endif
}
RDAudioConvert::ErrorCode RDAudioConvert::Stage1SndFile(const QString &dstfile,

View File

@ -152,6 +152,9 @@ class RDAudioConvert : public QObject
int (*lame_encode_flush)(lame_global_flags *,unsigned char *,int);
int (*lame_set_bWriteVbrTag)(lame_global_flags *, int);
#endif // HAVE_LAME
#ifdef HAVE_MP4_LIBS
DLMP4 dlmp4;
#endif
};

106
lib/rdmp4.cpp Normal file
View File

@ -0,0 +1,106 @@
// rdmp4.cpp
//
// Helpers for dealing with MP4 files.
//
// (C) Copyright 2014 Christopher Smowton <chris@smowton.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU Library General Public License
// version 2 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//
#ifdef HAVE_MP4_LIBS
#include <rdmp4.h>
#include <string.h>
MP4TrackId DLMP4::getMP4AACTrack(MP4FileHandle f)
{
uint32_t nTracks = this->MP4GetNumberOfTracks(f);
for(uint32_t trackIndex = 0; trackIndex < nTracks; ++trackIndex) {
MP4TrackId thisTrack = this->MP4FindTrackId(f, trackIndex);
const char* trackType = this->MP4GetTrackType(f, thisTrack);
if(trackType && !strcmp(trackType, MP4_AUDIO_TRACK_TYPE)) {
const char* dataName = this->MP4GetTrackMediaDataName(f, thisTrack);
// The M4A format is only currently useful for AAC in an M4A container:
if(dataName &&
(!strcasecmp(dataName, "mp4a")) &&
this->MP4GetTrackEsdsObjectTypeId(f, thisTrack) == MP4_MPEG4_AUDIO_TYPE) {
return thisTrack;
}
}
}
return MP4_INVALID_TRACK_ID;
}
#define check_dlsym(lval, libhandle, symname) \
*(void**)(&lval) = dlsym(libhandle, symname); \
if(!lval) return false;
bool DLMP4::load()
{
if(loadSuccess)
return true;
neaac_handle = dlopen("libfaad.so",RTLD_LAZY);
mp4_handle = dlopen("libmp4v2.so",RTLD_LAZY);
if(!neaac_handle || !mp4_handle)
return false;
check_dlsym(this->MP4Read, mp4_handle, "MP4Read");
check_dlsym(this->MP4GetTrackNumberOfSamples, mp4_handle, "MP4GetTrackNumberOfSamples");
check_dlsym(this->MP4GetSampleIdFromTime, mp4_handle, "MP4GetSampleIdFromTime");
check_dlsym(this->MP4GetTrackMaxSampleSize, mp4_handle, "MP4GetTrackMaxSampleSize");
check_dlsym(this->MP4GetTrackESConfiguration, mp4_handle, "MP4GetTrackESConfiguration");
check_dlsym(this->MP4ReadSample, mp4_handle, "MP4ReadSample");
check_dlsym(this->MP4GetTrackBitRate, mp4_handle, "MP4GetTrackBitRate");
check_dlsym(this->MP4GetTrackAudioChannels, mp4_handle, "MP4GetTrackAudioChannels");
check_dlsym(this->MP4GetTrackDuration, mp4_handle, "MP4GetTrackDuration");
check_dlsym(this->MP4ConvertFromTrackDuration, mp4_handle, "MP4ConvertFromTrackDuration");
check_dlsym(this->MP4GetTrackTimeScale, mp4_handle, "MP4GetTrackTimeScale");
check_dlsym(this->MP4GetNumberOfTracks, mp4_handle, "MP4GetNumberOfTracks");
check_dlsym(this->MP4FindTrackId, mp4_handle, "MP4FindTrackId");
check_dlsym(this->MP4GetTrackType, mp4_handle, "MP4GetTrackType");
check_dlsym(this->MP4GetTrackMediaDataName, mp4_handle, "MP4GetTrackMediaDataName");
check_dlsym(this->MP4GetTrackEsdsObjectTypeId, mp4_handle, "MP4GetTrackEsdsObjectTypeId");
check_dlsym(this->MP4TagsAlloc, mp4_handle, "MP4TagsAlloc");
check_dlsym(this->MP4TagsFetch, mp4_handle, "MP4TagsFetch");
check_dlsym(this->MP4TagsFree, mp4_handle, "MP4TagsFree");
check_dlsym(this->MP4Close, mp4_handle, "MP4Close");
check_dlsym(this->NeAACDecOpen, neaac_handle, "NeAACDecOpen");
check_dlsym(this->NeAACDecGetCurrentConfiguration, neaac_handle, "NeAACDecGetCurrentConfiguration");
check_dlsym(this->NeAACDecSetConfiguration, neaac_handle, "NeAACDecSetConfiguration");
check_dlsym(this->NeAACDecInit2, neaac_handle, "NeAACDecInit2");
check_dlsym(this->NeAACDecDecode, neaac_handle, "NeAACDecDecode");
check_dlsym(this->NeAACDecClose, neaac_handle, "NeAACDecClose");
loadSuccess = true;
}
#undef check_dlsym
#endif // HAVE_MP4_LIBS

95
lib/rdmp4.h Normal file
View File

@ -0,0 +1,95 @@
// rdmp4.h
//
// Helpers for dealing with MP4 files.
//
// (C) Copyright 2014 Christopher Smowton <chris@smowton.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU Library General Public License
// version 2 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//
#ifndef RDMP4_H
#define RDMP4_H
#ifdef HAVE_MP4_LIBS
#include <mp4v2/mp4v2.h>
#include <neaacdec.h>
struct DLMP4 {
DLMP4() : loadSuccess(false) {}
void *conv_neaac_handle;
void *conv_mp4_handle;
bool loadSuccess;
// MP4v2 Functions
MP4FileHandle (*MP4Read) (const char* fileName);
MP4SampleId (*MP4GetTrackNumberOfSamples) (MP4FileHandle hFile, MP4TrackId trackId);
MP4SampleId (*MP4GetSampleIdFromTime) (MP4FileHandle hFile, MP4TrackId trackId, MP4Timestamp when, bool wantSyncSample);
uint32_t (*MP4GetTrackMaxSampleSize) (MP4FileHandle hFile, MP4TrackId trackId);
bool (*MP4GetTrackESConfiguration) (MP4FileHandle hFile, MP4TrackId trackId, uint8_t** ppConfig, uint32_t* pConfigSize);
bool (*MP4ReadSample) (
MP4FileHandle hFile,
MP4TrackId trackId,
MP4SampleId sampleId,
uint8_t** ppBytes,
uint32_t* pNumBytes,
MP4Timestamp* pStartTime,
MP4Duration* pDuration,
MP4Duration* pRenderingOffset,
bool* pIsSyncSample);
uint32_t (*MP4GetTrackBitRate) (MP4FileHandle hFile, MP4TrackId trackId);
int (*MP4GetTrackAudioChannels) (MP4FileHandle hFile, MP4TrackId trackId);
MP4Duration (*MP4GetTrackDuration) (MP4FileHandle hFile, MP4TrackId trackId);
uint64_t (*MP4ConvertFromTrackDuration) (
MP4FileHandle hFile,
MP4TrackId trackId,
MP4Duration duration,
uint32_t timeScale);
uint32_t (*MP4GetTrackTimeScale) (MP4FileHandle hFile, MP4TrackId trackId);
uint32_t (*MP4GetNumberOfTracks) (MP4FileHandle hFile);
MP4TrackId (*MP4FindTrackId) (MP4FileHandle hFile, uint32_t trackIdx);
const char* (*MP4GetTrackType) (MP4FileHandle hFile, MP4TrackId);
const char* (*MP4GetTrackMediaDataName) (MP4FileHandle hFile, MP4TrackId);
uint8_t (*MP4GetTrackEsdsObjectTypeId) (MP4FileHandle hFile, MP4TrackId);
const MP4Tags* (*MP4TagsAlloc) (void);
bool (*MP4TagsFetch) (const MP4Tags* tags, MP4FileHandle hFile);
void (*MP4TagsFree) (const MP4Tags* tags);
void (*MP4Close) (MP4FileHandle hFile, uint32_t flags);
// libfaad / NeAACDec functions
NeAACDecHandle (*NeAACDecOpen) (void);
NeAACDecConfigurationPtr (*NeAACDecGetCurrentConfiguration) (NeAACDecHandle hDecoder);
unsigned char (*NeAACDecSetConfiguration) (NeAACDecHandle hDecoder, NeAACDecConfigurationPtr config);
char (*NeAACDecInit2)(NeAACDecHandle hDecoder,
unsigned char *pBuffer,
unsigned long SizeOfDecoderSpecificInfo,
unsigned long *samplerate,
unsigned char *channels);
void* (*NeAACDecDecode) (NeAACDecHandle hDecoder,
NeAACDecFrameInfo *hInfo,
unsigned char *buffer,
unsigned long buffer_size);
void (*NeAACDecClose) (NeAACDecHandle hDecoder);
// Helper functions:
MP4TrackId getMP4AACTrack(MP4FileHandle f);
bool load();
};
#endif // HAVE_MP4_LIBS
#endif // RDMP4_H

View File

@ -47,8 +47,9 @@
#include <rd.h>
#include <rdwavefile.h>
#include <rdconf.h>
#include <rdmp4.h>
#ifdef HAVE_MP4V2
#ifdef HAVE_MP4_LIBS
#include <mp4v2/mp4v2.h>
#endif
@ -328,55 +329,36 @@ bool RDWaveFile::openWave(RDWaveData *data)
case RDWaveFile::M4A:
{
#ifdef HAVE_MP4V2
#ifdef HAVE_MP4_LIBS
// MP4 libs must already be loaded by now for file to have that type.
assert(dlmp4.load());
format_tag=WAVE_FORMAT_M4A;
MP4FileHandle f = MP4Read(getName());
MP4FileHandle f = dlmp4.MP4Read(getName());
if(f == MP4_INVALID_FILE_HANDLE)
return false;
// Find an audio track, and populate sample rate, bits/sample etc.
uint32_t nTracks = MP4GetNumberOfTracks(f);
MP4TrackId audioTrack = MP4_INVALID_TRACK_ID;
for(uint32_t trackIndex = 0; trackIndex < nTracks && audioTrack == MP4_INVALID_TRACK_ID; ++trackIndex) {
MP4TrackId thisTrack = MP4FindTrackId(f, trackIndex);
const char* trackType = MP4GetTrackType(f, thisTrack);
if(trackType && !strcmp(trackType, MP4_AUDIO_TRACK_TYPE)) {
const char* dataName = MP4GetTrackMediaDataName(f, thisTrack);
// The M4A format is only currently useful for AAC in an M4A container:
if(dataName &&
(!strcasecmp(dataName, "mp4a")) &&
MP4GetTrackEsdsObjectTypeId(f, thisTrack) == MP4_MPEG4_AUDIO_TYPE) {
audioTrack = thisTrack;
}
}
}
MP4TrackId audioTrack = dlmp4.getMP4AACTrack(f);
if(audioTrack == MP4_INVALID_TRACK_ID) {
MP4Close(f);
dlmp4.MP4Close(f, 0);
return false;
}
// Found audio track. Get audio data:
avg_bytes_per_sec = MP4GetTrackBitRate(f, audioTrack);
channels = MP4GetTrackAudioChannels(f, audioTrack);
avg_bytes_per_sec = dlmp4.MP4GetTrackBitRate(f, audioTrack);
channels = dlmp4.MP4GetTrackAudioChannels(f, audioTrack);
MP4Duration trackDuration = MP4GetTrackDuration(f, audioTrack);
ext_time_length = (unsigned)MP4ConvertFromTrackDuration(f, audioTrack, trackDuration,
MP4_MSECS_TIME_SCALE);
MP4Duration trackDuration = dlmp4.MP4GetTrackDuration(f, audioTrack);
ext_time_length = (unsigned)dlmp4.MP4ConvertFromTrackDuration(f, audioTrack, trackDuration,
MP4_MSECS_TIME_SCALE);
time_length = ext_time_length / 1000;
samples_per_sec = MP4GetTrackTimeScale(f, audioTrack);
samples_per_sec = dlmp4.MP4GetTrackTimeScale(f, audioTrack);
bits_per_sample = 16;
data_start = 0;
sample_length = MP4GetTrackNumberOfSamples(f, audioTrack);
sample_length = dlmp4.MP4GetTrackNumberOfSamples(f, audioTrack);
data_length = sample_length * 2 * channels;
data_chunk = true;
format_chunk = true;
@ -386,8 +368,8 @@ bool RDWaveFile::openWave(RDWaveData *data)
if(wave_data) {
const MP4Tags* tags = MP4TagsAlloc();
MP4TagsFetch(tags, f);
const MP4Tags* tags = dlmp4.MP4TagsAlloc();
dlmp4.MP4TagsFetch(tags, f);
wave_data->setMetadataFound(true);
@ -400,11 +382,11 @@ bool RDWaveFile::openWave(RDWaveData *data)
if(tags->album)
wave_data->setAlbum(tags->album);
MP4TagsFree(tags);
dlmp4.MP4TagsFree(tags);
}
MP4Close(f);
dlmp4.MP4Close(f, 0);
return true;
@ -2308,11 +2290,13 @@ bool RDWaveFile::IsAiff(int fd)
bool RDWaveFile::IsM4A(int fd)
{
#ifdef HAVE_MP4V2
MP4FileHandle f = MP4Read(getName());
#ifdef HAVE_MP4_LIBS
if(!dlmp4.load())
return false;
MP4FileHandle f = dlmp4.MP4Read(getName());
bool ret = f != MP4_INVALID_FILE_HANDLE;
if(ret)
MP4Close(f);
dlmp4.MP4Close(f, 0);
return ret;
#else
return false;

View File

@ -1241,6 +1241,10 @@ class RDWaveFile
ogg_page ogg_pg;
ogg_packet ogg_pack;
#endif // HAVE_VORBIS
#ifdef HAVE_MP4_LIBS
DLMP4 dlmp4;
#endif
};